From 558468090bcfd35867a14b9fa8df1d051764925d Mon Sep 17 00:00:00 2001 From: Thomas Vitale Date: Tue, 15 Jun 2021 22:00:06 +0200 Subject: [PATCH] Load ReactiveJwtAuthenticationConverter bean in OAuth2 Resource Server config When a bean of type ReactiveJwtAuthenticationConverter is defined, the OAuth2 Resource Server configuration will use it automatically when no other converter is defined through the DSL. Closes gh-9698 --- .../config/web/server/ServerHttpSecurity.java | 24 ++++++-- .../server/OAuth2ResourceServerSpecTests.java | 55 ++++++++++++++++++- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index b4e232e7782..aa492bf2762 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -91,10 +91,9 @@ import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager; import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager; -import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; +import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler; @@ -1497,6 +1496,13 @@ private T getBeanOrNull(ResolvableType type) { return null; } + private String[] getBeanNamesForTypeOrEmpty(Class beanClass) { + if (this.context == null) { + return new String[0]; + } + return this.context.getBeanNamesForType(beanClass); + } + protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } @@ -3813,8 +3819,7 @@ public class JwtSpec { private ReactiveJwtDecoder jwtDecoder; - private Converter> jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverterAdapter( - new JwtAuthenticationConverter()); + private Converter> jwtAuthenticationConverter; /** * Configures the {@link ReactiveAuthenticationManager} to use @@ -3892,7 +3897,16 @@ protected ReactiveJwtDecoder getJwtDecoder() { } protected Converter> getJwtAuthenticationConverter() { - return this.jwtAuthenticationConverter; + if (this.jwtAuthenticationConverter != null) { + return this.jwtAuthenticationConverter; + } + + if (getBeanNamesForTypeOrEmpty(ReactiveJwtAuthenticationConverter.class).length > 0) { + return getBean(ReactiveJwtAuthenticationConverter.class); + } + else { + return new ReactiveJwtAuthenticationConverter(); + } } private ReactiveAuthenticationManager getAuthenticationManager() { diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java index 0ac36558c93..f47d77aa905 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,6 +64,7 @@ import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; @@ -478,6 +479,58 @@ public void getJwtDecoderWhenNoBeansAndNoDslWiredThenWiringException() { assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(() -> jwt.getJwtDecoder()); } + @Test + public void getJwtAuthenticationConverterWhenBeanWiredAndDslWiredThenDslTakesPrecedence() { + GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); + ServerHttpSecurity http = new ServerHttpSecurity(); + http.setApplicationContext(context); + ReactiveJwtAuthenticationConverter beanWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); + ReactiveJwtAuthenticationConverter dslWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); + context.registerBean(ReactiveJwtAuthenticationConverter.class, () -> beanWiredJwtAuthenticationConverter); + ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec jwt = http.oauth2ResourceServer().jwt(); + jwt.jwtAuthenticationConverter(dslWiredJwtAuthenticationConverter); + assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(dslWiredJwtAuthenticationConverter); + } + + @Test + public void getJwtAuthenticationConverterWhenTwoBeansWiredAndDslWiredThenDslTakesPrecedence() { + GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); + ServerHttpSecurity http = new ServerHttpSecurity(); + http.setApplicationContext(context); + ReactiveJwtAuthenticationConverter beanWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); + ReactiveJwtAuthenticationConverter dslWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); + context.registerBean("firstJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, + () -> beanWiredJwtAuthenticationConverter); + context.registerBean("secondJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, + () -> beanWiredJwtAuthenticationConverter); + ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec jwt = http.oauth2ResourceServer().jwt(); + jwt.jwtAuthenticationConverter(dslWiredJwtAuthenticationConverter); + assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(dslWiredJwtAuthenticationConverter); + } + + @Test + public void getJwtAuthenticationConverterWhenTwoBeansWiredThenThrowsWiringException() { + GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); + ServerHttpSecurity http = new ServerHttpSecurity(); + http.setApplicationContext(context); + ReactiveJwtAuthenticationConverter beanWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); + context.registerBean("firstJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, + () -> beanWiredJwtAuthenticationConverter); + context.registerBean("secondJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, + () -> beanWiredJwtAuthenticationConverter); + ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec jwt = http.oauth2ResourceServer().jwt(); + assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(jwt::getJwtAuthenticationConverter); + } + + @Test + public void getJwtAuthenticationConverterWhenNoBeansAndNoDslWiredThenDefaultConverter() { + GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); + ServerHttpSecurity http = new ServerHttpSecurity(); + http.setApplicationContext(context); + ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec jwt = http.oauth2ResourceServer().jwt(); + assertThat(jwt.getJwtAuthenticationConverter()).isInstanceOf(ReactiveJwtAuthenticationConverter.class); + } + @Test public void introspectWhenValidThenReturnsOk() { this.spring.register(IntrospectionConfig.class, RootController.class).autowire();