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
This commit is contained in:
parent
6330190913
commit
beba1f176a
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue