diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java index a4f80a8925..f024133560 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec; +import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders; @@ -43,6 +44,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain; * * @author Madhura Bhave * @author Artsiom Yudovin + * @author HaiTao Zhang */ @Configuration(proxyBeanMethods = false) class ReactiveOAuth2ResourceServerJwkConfiguration { @@ -60,7 +62,13 @@ class ReactiveOAuth2ResourceServerJwkConfiguration { @Bean @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") ReactiveJwtDecoder jwtDecoder() { - return new NimbusReactiveJwtDecoder(this.properties.getJwkSetUri()); + NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = new NimbusReactiveJwtDecoder( + this.properties.getJwkSetUri()); + String issuerUri = this.properties.getIssuerUri(); + if (issuerUri != null) { + nimbusReactiveJwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuerUri)); + } + return nimbusReactiveJwtDecoder; } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java index 1bcb68eee8..98c27f2fde 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java @@ -35,6 +35,7 @@ import org.springframework.security.config.annotation.web.configurers.oauth2.ser import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoders; +import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; /** @@ -44,8 +45,10 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; * * @author Madhura Bhave * @author Artsiom Yudovin + * @author HaiTao Zhang */ @Configuration(proxyBeanMethods = false) + class OAuth2ResourceServerJwtConfiguration { @Configuration(proxyBeanMethods = false) @@ -61,8 +64,13 @@ class OAuth2ResourceServerJwtConfiguration { @Bean @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") JwtDecoder jwtDecoderByJwkKeySetUri() { - return NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri()) + NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri()) .jwsAlgorithm(SignatureAlgorithm.from(this.properties.getJwsAlgorithm())).build(); + String issuerUri = this.properties.getIssuerUri(); + if (issuerUri != null) { + nimbusJwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuerUri)); + } + return nimbusJwtDecoder; } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java index 7275b26961..55d1d0a840 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -42,6 +43,10 @@ import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtIssuerValidator; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; @@ -318,6 +323,30 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { "Only one of jwt.public-key-location and opaquetoken.introspection-uri should be configured.")); } + @SuppressWarnings("unchecked") + @Test + void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponse(cleanIssuerPath); + this.contextRunner + .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", + "spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" + this.server.getHostName() + ":" + + this.server.getPort() + "/" + path) + .run((context) -> { + assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); + ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class); + DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils + .getField(reactiveJwtDecoder, "jwtValidator"); + Collection> tokenValidators = (Collection>) ReflectionTestUtils + .getField(jwtValidator, "tokenValidators"); + assertThat(tokenValidators.stream()).hasAtLeastOneElementOfType(JwtIssuerValidator.class); + }); + } + private void assertFilterConfiguredWithJwtAuthenticationManager(AssertableReactiveWebApplicationContext context) { MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java index a345e055b5..77e0198461 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java @@ -15,6 +15,7 @@ */ package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -41,7 +42,11 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtIssuerValidator; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionAuthenticationToken; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; @@ -320,6 +325,30 @@ class OAuth2ResourceServerAutoConfigurationTests { "Only one of jwt.public-key-location and opaquetoken.introspection-uri should be configured.")); } + @SuppressWarnings("unchecked") + @Test + void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponse(cleanIssuerPath); + this.contextRunner + .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", + "spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" + this.server.getHostName() + ":" + + this.server.getPort() + "/" + path) + .run((context) -> { + assertThat(context).hasSingleBean(JwtDecoder.class); + JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class); + DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils + .getField(jwtDecoder, "jwtValidator"); + Collection> tokenValidators = (Collection>) ReflectionTestUtils + .getField(jwtValidator, "tokenValidators"); + assertThat(tokenValidators.stream()).hasAtLeastOneElementOfType(JwtIssuerValidator.class); + }); + } + private Filter getBearerTokenFilter(AssertableWebApplicationContext context) { FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); List filterChains = filterChain.getFilterChains();