From beba1f176a93f80c179e7d0970df93ce910f8a9f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 11 Dec 2023 10:45:15 +0000 Subject: [PATCH] Do not enable WebFlux security unless other configuration is active Following the changes in gh-37504, the reactive resource server auto-configuration could enable WebFlux security in situations where it was otherwise in active. This could then result in an application failing to start as no authentication manager is available. This commit updates the configurations that enable WebFlux security so that they fully back off unless their related configurations are active. Previously, only the configuration of the SecurityWebFilterChain would back off. This has been expanded to cover `@EnableWebFluxSecurity` as well. This has required splitting the configuration classes up so that the condition evaluation order can be controlled more precisely. We need to ensure that the JWT decoder bean or the opaque token introspector bean has been defined before evaluation of the conditions for `@EnableWebFluxSecurity`. Without this control, the import through `@EnableWebFluxSecurity` in one location where the conditions do not matchcan prevent a successful import in another where they do. Fixes gh-38713 --- ...OAuth2ResourceServerAutoConfiguration.java | 6 +++-- ...tiveOAuth2ResourceServerConfiguration.java | 26 ++++++++++++++----- ...eOAuth2ResourceServerJwkConfiguration.java | 2 +- ...esourceServerOpaqueTokenConfiguration.java | 2 +- ...2ResourceServerAutoConfigurationTests.java | 11 +++++++- 5 files changed, 35 insertions(+), 12 deletions(-) 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 78f28f94b81..92c02ca9d43 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -40,7 +40,9 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux @ConditionalOnClass({ EnableWebFluxSecurity.class }) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @Import({ ReactiveOAuth2ResourceServerConfiguration.JwtConfiguration.class, - ReactiveOAuth2ResourceServerConfiguration.OpaqueTokenConfiguration.class }) + ReactiveOAuth2ResourceServerConfiguration.OpaqueTokenConfiguration.class, + ReactiveOAuth2ResourceServerConfiguration.JwtWebSecurityConfiguration.class, + ReactiveOAuth2ResourceServerConfiguration.OpaqueTokenWebSecurityConfiguration.class }) public class ReactiveOAuth2ResourceServerAutoConfiguration { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerConfiguration.java index d4f5388f041..6cedf7e711c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -24,8 +24,8 @@ import org.springframework.security.oauth2.server.resource.authentication.Bearer import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; /** - * Configuration classes for OAuth2 Resource Server These should be {@code @Import} in a - * regular auto-configuration class to guarantee their order of execution. + * Configuration classes for OAuth2 Resource Server. These should be {@code @Import}ed in + * a regular auto-configuration class to guarantee their order of execution. * * @author Madhura Bhave */ @@ -33,18 +33,30 @@ class ReactiveOAuth2ResourceServerConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class }) - @Import({ ReactiveOAuth2ResourceServerJwkConfiguration.JwtConfiguration.class, - ReactiveOAuth2ResourceServerJwkConfiguration.WebSecurityConfiguration.class }) + @Import(ReactiveOAuth2ResourceServerJwkConfiguration.JwtConfiguration.class) static class JwtConfiguration { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class }) + @Import(ReactiveOAuth2ResourceServerJwkConfiguration.WebSecurityConfiguration.class) + static class JwtWebSecurityConfiguration { + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveOpaqueTokenIntrospector.class }) - @Import({ ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class, - ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.WebSecurityConfiguration.class }) + @Import(ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class) static class OpaqueTokenConfiguration { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveOpaqueTokenIntrospector.class }) + @Import(ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.WebSecurityConfiguration.class) + static class OpaqueTokenWebSecurityConfiguration { + + } + } 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 31cb13aa60c..0c10d879b97 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 @@ -164,11 +164,11 @@ class ReactiveOAuth2ResourceServerJwkConfiguration { } @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(ReactiveJwtDecoder.class) @ConditionalOnMissingBean(SecurityWebFilterChain.class) static class WebSecurityConfiguration { @Bean - @ConditionalOnBean(ReactiveJwtDecoder.class) SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveJwtDecoder jwtDecoder) { http.authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated()); http.oauth2ResourceServer((server) -> customDecoder(server, jwtDecoder)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java index dbeb778d876..6612dfe703c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java @@ -56,10 +56,10 @@ class ReactiveOAuth2ResourceServerOpaqueTokenConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(SecurityWebFilterChain.class) + @ConditionalOnBean(ReactiveOpaqueTokenIntrospector.class) static class WebSecurityConfiguration { @Bean - @ConditionalOnBean(ReactiveOpaqueTokenIntrospector.class) SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated()); http.oauth2ResourceServer((resourceServer) -> resourceServer.opaqueToken(withDefaults())); 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 9583efcbc45..d60b395c1bb 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 @@ -43,6 +43,8 @@ import org.mockito.InOrder; import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; +import org.springframework.boot.logging.LogLevel; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -73,6 +75,7 @@ import org.springframework.security.oauth2.server.resource.authentication.Opaque import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; import org.springframework.security.web.server.MatcherSecurityWebFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.authentication.AuthenticationWebFilter; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.server.WebFilter; @@ -116,10 +119,16 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { } } + @Test + void autoConfigurationDoesNotEnableWebSecurityWithoutJwtDecoderOrTokenIntrospector() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(WebFilterChainProxy.class)); + } + @Test void autoConfigurationShouldConfigureResourceServer() { this.contextRunner .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com") + .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) .run((context) -> { assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class); assertFilterConfiguredWithJwtAuthenticationManager(context); @@ -385,7 +394,7 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { @Test void autoConfigurationWhenIntrospectionUriAvailableShouldConfigureIntrospectionClient() { - this.contextRunner + this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) .withPropertyValues( "spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com", "spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id",