diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfiguration.java index bce0c992139..7f6279dd1aa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -38,7 +39,7 @@ import org.springframework.security.oauth2.server.resource.introspection.Reactiv * @since 2.1.0 */ @Configuration(proxyBeanMethods = false) -@AutoConfigureBefore(ReactiveSecurityAutoConfiguration.class) +@AutoConfigureBefore({ ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class }) @EnableConfigurationProperties(OAuth2ResourceServerProperties.class) @ConditionalOnClass({ EnableWebFluxSecurity.class }) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfiguration.java index d5e05b48906..c4cd3c5b828 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -36,7 +37,7 @@ import org.springframework.security.oauth2.server.resource.introspection.OAuth2T * @since 2.1.0 */ @Configuration(proxyBeanMethods = false) -@AutoConfigureBefore(SecurityAutoConfiguration.class) +@AutoConfigureBefore({ SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class }) @EnableConfigurationProperties(OAuth2ResourceServerProperties.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java index b562b505547..d0b1ed19e25 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java @@ -48,7 +48,9 @@ import org.springframework.util.StringUtils; */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ ReactiveAuthenticationManager.class }) -@ConditionalOnMissingBean({ ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class }) +@ConditionalOnMissingBean(value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class }, type = { + "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder", + "org.springframework.security.oauth2.server.resource.introspection.ReactiveOAuth2TokenIntrospectionClient" }) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) public class ReactiveUserDetailsServiceAutoConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java index aaa078cd2af..5ba125a2daf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java @@ -55,7 +55,10 @@ import org.springframework.util.StringUtils; @Configuration(proxyBeanMethods = false) @ConditionalOnClass(AuthenticationManager.class) @ConditionalOnBean(ObjectPostProcessor.class) -@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class }) +@ConditionalOnMissingBean( + value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class }, + type = { "org.springframework.security.oauth2.jwt.JwtDecoder", + "org.springframework.security.oauth2.server.resource.introspection.OAuth2TokenIntrospectionClient" }) public class UserDetailsServiceAutoConfiguration { private static final String NOOP_PASSWORD_PREFIX = "{noop}"; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java index 3974462c323..dc8ea647b69 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java @@ -34,6 +34,8 @@ import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; +import org.springframework.security.oauth2.server.resource.introspection.ReactiveOAuth2TokenIntrospectionClient; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -42,6 +44,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link ReactiveUserDetailsServiceAutoConfiguration}. * * @author Madhura Bhave + * @author HaiTao Zhang */ class ReactiveUserDetailsServiceAutoConfigurationTests { @@ -73,6 +76,24 @@ class ReactiveUserDetailsServiceAutoConfigurationTests { .run((context) -> assertThat(context).getBean(ReactiveUserDetailsService.class).isNull()); } + @Test + void doesNotConfigureDefaultUserIfResourceServerWithJWTIsUsed() { + this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class, JwtDecoderConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); + assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class); + }); + } + + @Test + void doesNotConfigureDefaultUserIfResourceServerWithOpaqueIsUsed() { + this.contextRunner.withUserConfiguration(ReactiveOAuth2TokenIntrospectionClientConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ReactiveOAuth2TokenIntrospectionClient.class); + assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class); + }); + } + @Test void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() { this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run(((context) -> { @@ -149,4 +170,24 @@ class ReactiveUserDetailsServiceAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class JwtDecoderConfiguration { + + @Bean + ReactiveJwtDecoder jwtDecoder() { + return mock(ReactiveJwtDecoder.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ReactiveOAuth2TokenIntrospectionClientConfiguration { + + @Bean + ReactiveOAuth2TokenIntrospectionClient introspectionClient() { + return mock(ReactiveOAuth2TokenIntrospectionClient.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java index 78c8eb85deb..f203fba10af 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java @@ -42,6 +42,8 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2TokenIntrospectionClient; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import static org.assertj.core.api.Assertions.assertThat; @@ -51,6 +53,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link UserDetailsServiceAutoConfiguration}. * * @author Madhura Bhave + * @author HaiTao Zhang */ @ExtendWith(OutputCaptureExtension.class) class UserDetailsServiceAutoConfigurationTests { @@ -99,6 +102,22 @@ class UserDetailsServiceAutoConfigurationTests { }); } + @Test + void defaultUserNotCreatedIfResourceServerWithOpaqueIsUsed() { + this.contextRunner.withUserConfiguration(TestConfigWithIntrospectionClient.class).run((context) -> { + assertThat(context).hasSingleBean(OAuth2TokenIntrospectionClient.class); + assertThat(context).doesNotHaveBean(UserDetailsService.class); + }); + } + + @Test + void defaultUserNotCreatedIfResourceServerWithJWTIsUsed() { + this.contextRunner.withUserConfiguration(TestConfigWithJwtDecoder.class).run((context) -> { + assertThat(context).hasSingleBean(JwtDecoder.class); + assertThat(context).doesNotHaveBean(UserDetailsService.class); + }); + } + @Test void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() { this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run(((context) -> { @@ -208,6 +227,28 @@ class UserDetailsServiceAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + @Import(TestSecurityConfiguration.class) + static class TestConfigWithJwtDecoder { + + @Bean + JwtDecoder jwtDecoder() { + return mock(JwtDecoder.class); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(TestSecurityConfiguration.class) + static class TestConfigWithIntrospectionClient { + + @Bean + OAuth2TokenIntrospectionClient introspectionClient() { + return mock(OAuth2TokenIntrospectionClient.class); + } + + } + @Configuration(proxyBeanMethods = false) @Import(TestSecurityConfiguration.class) static class TestConfigWithAuthenticationManagerBuilder {