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 e0c1a23bb7b..9d77b606615 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 @@ -22,6 +22,7 @@ import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2Res import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +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; @@ -31,6 +32,7 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders; * * @author Madhura Bhave * @author Artsiom Yudovin + * @author HaiTao Zhang */ @Configuration class ReactiveOAuth2ResourceServerJwkConfiguration { @@ -45,7 +47,13 @@ class ReactiveOAuth2ResourceServerJwkConfiguration { @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") @ConditionalOnMissingBean public ReactiveJwtDecoder jwtDecoder() { - return new NimbusReactiveJwtDecoder(this.properties.getJwt().getJwkSetUri()); + NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = new NimbusReactiveJwtDecoder( + this.properties.getJwt().getJwkSetUri()); + String issuerUri = this.properties.getJwt().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 18adee01fa2..4dcd5ab34d5 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 @@ -24,6 +24,7 @@ import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; 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.NimbusJwtDecoderJwkSupport; /** @@ -32,6 +33,7 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport; * * @author Madhura Bhave * @author Artsiom Yudovin + * @author HaiTao Zhang */ @Configuration class OAuth2ResourceServerJwtConfiguration { @@ -46,7 +48,13 @@ class OAuth2ResourceServerJwtConfiguration { @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") @ConditionalOnMissingBean public JwtDecoder jwtDecoderByJwkKeySetUri() { - return new NimbusJwtDecoderJwkSupport(this.properties.getJwt().getJwkSetUri()); + NimbusJwtDecoderJwkSupport nimbusJwtDecoder = new NimbusJwtDecoderJwkSupport( + this.properties.getJwt().getJwkSetUri()); + String issuerUri = this.properties.getJwt().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 0e4c7f66e79..fdc97f9c474 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; @@ -60,6 +65,7 @@ import static org.mockito.Mockito.mock; * * @author Madhura Bhave * @author Artsiom Yudovin + * @author HaiTao Zhang */ public class ReactiveOAuth2ResourceServerAutoConfigurationTests { @@ -163,6 +169,30 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests { }); } + @SuppressWarnings("unchecked") + @Test + public 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 a78a0e760a7..bf60017cf07 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; @@ -40,12 +41,17 @@ 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.jwt.NimbusJwtDecoderJwkSupport; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -55,6 +61,7 @@ import static org.mockito.Mockito.mock; * * @author Madhura Bhave * @author Artsiom Yudovin + * @author HaiTao Zhang */ public class OAuth2ResourceServerAutoConfigurationTests { @@ -148,6 +155,30 @@ public class OAuth2ResourceServerAutoConfigurationTests { .run((context) -> assertThat(getBearerTokenFilter(context)).isNull()); } + @SuppressWarnings("unchecked") + @Test + public 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();