Make security auto-configs back off when SecurityFilterChain present

Closes gh-22739
This commit is contained in:
Madhura Bhave 2020-08-14 12:21:35 -07:00
parent c9b8a05321
commit bbbbe8e4d2
18 changed files with 264 additions and 90 deletions

View File

@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.security.servlet;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -29,28 +31,46 @@ import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAu
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.SecurityFilterChain;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security when actuator is
* on the classpath. Specifically, it permits access to the health and info endpoints
* while securing everything else.
* on the classpath. It allows unauthenticated access to the {@link HealthEndpoint} and
* {@link InfoEndpoint}. If the user specifies their own
* {@link WebSecurityConfigurerAdapter} or {@link SecurityFilterChain} bean, this will
* back-off completely and the user should specify all the bits that they want to
* configure as part of the custom security configuration.
*
* @author Madhura Bhave
* @since 2.1.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnClass({ SecurityFilterChain.class, WebSecurityConfigurerAdapter.class })
@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@AutoConfigureBefore(SecurityAutoConfiguration.class)
@AutoConfigureAfter({ HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class, OAuth2ClientAutoConfiguration.class,
OAuth2ResourceServerAutoConfiguration.class, Saml2RelyingPartyAutoConfiguration.class })
@Import({ ManagementWebSecurityConfigurerAdapter.class, WebSecurityEnablerConfiguration.class })
public class ManagementWebSecurityAutoConfiguration {
@Configuration(proxyBeanMethods = false)
static class ManagementWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> {
requests.requestMatchers(EndpointRequest.to(HealthEndpoint.class, InfoEndpoint.class)).permitAll();
requests.anyRequest().authenticated();
});
http.formLogin(Customizer.withDefaults());
http.httpBasic(Customizer.withDefaults());
}
}
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2012-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.security.servlet;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* The default configuration for web security when the actuator dependency is on the
* classpath. It is different from
* {@link org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration}
* in that it allows unauthenticated access to the {@link HealthEndpoint} and
* {@link InfoEndpoint}. If the user specifies their own
* {@link WebSecurityConfigurerAdapter}, this will back-off completely and the user should
* specify all the bits that they want to configure as part of the custom security
* configuration.
*
* @author Madhura Bhave
*/
@Configuration(proxyBeanMethods = false)
class ManagementWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> {
requests.requestMatchers(EndpointRequest.to(HealthEndpoint.class, InfoEndpoint.class)).permitAll();
requests.anyRequest().authenticated();
});
http.formLogin(Customizer.withDefaults());
http.httpBasic(Customizer.withDefaults());
}
}

View File

@ -30,8 +30,10 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockFilterChain;
@ -42,6 +44,7 @@ import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@ -85,6 +88,15 @@ class ManagementWebSecurityAutoConfigurationTests {
});
}
@Test
void autoConfigIsConditionalOnSecurityFilterChainClass() {
this.contextRunner.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class)).run((context) -> {
assertThat(context).doesNotHaveBean(ManagementWebSecurityAutoConfiguration.class);
HttpStatus status = getResponseStatus(context, "/actuator/health");
assertThat(status).isEqualTo(HttpStatus.UNAUTHORIZED);
});
}
@Test
void usesMatchersBasedOffConfiguredActuatorBasePath() {
this.contextRunner.withPropertyValues("management.endpoints.web.base-path=/").run((context) -> {
@ -103,11 +115,20 @@ class ManagementWebSecurityAutoConfigurationTests {
});
}
@Test
void backsOffIfSecurityFilterChainBeanIsPresent() {
this.contextRunner.withUserConfiguration(TestSecurityFilterChainConfig.class).run((context) -> {
assertThat(context.getBeansOfType(SecurityFilterChain.class).size()).isEqualTo(1);
assertThat(context.containsBean("testSecurityFilterChain")).isTrue();
});
}
@Test
void backOffIfOAuth2ResourceServerAutoConfigurationPresent() {
this.contextRunner.withConfiguration(AutoConfigurations.of(OAuth2ResourceServerAutoConfiguration.class))
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://authserver")
.run((context) -> assertThat(context).doesNotHaveBean(ManagementWebSecurityConfigurerAdapter.class));
.run((context) -> assertThat(context).doesNotHaveBean(
ManagementWebSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter.class));
}
@Test
@ -118,7 +139,8 @@ class ManagementWebSecurityAutoConfigurationTests {
"spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.sign-request=false",
"spring.security.saml2.relyingparty.registration.simplesamlphp.identityprovider.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
"spring.security.saml2.relyingparty.registration.simplesamlphp.identityprovider.verification.credentials[0].certificate-location=classpath:saml/certificate-location")
.run((context) -> assertThat(context).doesNotHaveBean(ManagementWebSecurityConfigurerAdapter.class));
.run((context) -> assertThat(context).doesNotHaveBean(
ManagementWebSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter.class));
}
private HttpStatus getResponseStatus(AssertableWebApplicationContext context, String path)
@ -149,4 +171,15 @@ class ManagementWebSecurityAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class TestSecurityFilterChainConfig {
@Bean
SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
return http.antMatcher("/**").authorizeRequests((authorize) -> authorize.anyRequest().authenticated())
.build();
}
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -28,6 +29,7 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.web.SecurityFilterChain;
/**
* {@link WebSecurityConfigurerAdapter} to add OAuth client support.
@ -52,7 +54,8 @@ class OAuth2WebSecurityConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnClass({ SecurityFilterChain.class, WebSecurityConfigurerAdapter.class })
@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
static class OAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override

View File

@ -21,6 +21,7 @@ import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition;
@ -37,6 +38,7 @@ 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;
import org.springframework.security.web.SecurityFilterChain;
/**
* Configures a {@link JwtDecoder} when a JWK Set URI, OpenID Connect Issuer URI or Public
@ -96,7 +98,8 @@ class OAuth2ResourceServerJwtConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnClass({ SecurityFilterChain.class, WebSecurityConfigurerAdapter.class })
@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
static class OAuth2WebSecurityConfigurerAdapter {
@Bean

View File

@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
@ -26,6 +27,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.web.SecurityFilterChain;
/**
* Configures a {@link OpaqueTokenIntrospector} when a token introspection endpoint is
@ -52,7 +54,8 @@ class OAuth2ResourceServerOpaqueTokenConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnClass({ SecurityFilterChain.class, WebSecurityConfigurerAdapter.class })
@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
static class OAuth2WebSecurityConfigurerAdapter {
@Bean

View File

@ -17,11 +17,13 @@
package org.springframework.boot.autoconfigure.security.saml2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;
/**
* {@link WebSecurityConfigurerAdapter} configuration for Spring Security's relying party
@ -30,11 +32,12 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
* @author Madhura Bhave
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
@ConditionalOnBean(RelyingPartyRegistrationRepository.class)
@ConditionalOnClass({ SecurityFilterChain.class, WebSecurityConfigurerAdapter.class })
class Saml2LoginConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
static class Saml2LoginConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override

View File

@ -24,22 +24,23 @@ import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.SecurityFilterChain;
/**
* The default configuration for web security. It relies on Spring Security's
* content-negotiation strategy to determine what sort of authentication to use. If the
* user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off
* completely and the users should specify all the bits that they want to configure as
* part of the custom security configuration.
* user specifies their own {@link WebSecurityConfigurerAdapter} or
* {@link SecurityFilterChain} bean, this will back-off completely and the users should
* specify all the bits that they want to configure as part of the custom security
* configuration.
*
* @author Madhura Bhave
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnClass({ SecurityFilterChain.class, WebSecurityConfigurerAdapter.class })
@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {
class SpringBootWebSecurityConfiguration {
@Configuration(proxyBeanMethods = false)
@Order(SecurityProperties.BASIC_AUTH_ORDER)

View File

@ -16,31 +16,28 @@
package org.springframework.boot.autoconfigure.security.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* If there is a bean of type WebSecurityConfigurerAdapter, this adds the
* {@link EnableWebSecurity @EnableWebSecurity} annotation. This will make sure that the
* annotation is present with default security auto-configuration and also if the user
* adds custom security and forgets to add the annotation. If
* {@link EnableWebSecurity @EnableWebSecurity} has already been added or if a bean with
* name {@value BeanIds#SPRING_SECURITY_FILTER_CHAIN} has been configured by the user,
* this will back-off.
* Adds the{@link EnableWebSecurity @EnableWebSecurity} annotation if Spring Security is
* on the classpath. This will make sure that the annotation is present with default
* security auto-configuration and also if the user adds custom security and forgets to
* add the annotation. If {@link EnableWebSecurity @EnableWebSecurity} has already been
* added or if a bean with name {@value BeanIds#SPRING_SECURITY_FILTER_CHAIN} has been
* configured by the user, this will back-off.
*
* @author Madhura Bhave
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
class WebSecurityEnablerConfiguration {
}

View File

@ -24,6 +24,7 @@ import javax.servlet.Filter;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@ -31,6 +32,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
@ -115,7 +117,7 @@ class OAuth2WebSecurityConfigurationTests {
}
@Test
void securityConfigurerBacksOffWhenOtherWebSecurityAdapterPresent() {
void securityConfigurerBacksOffBacksOffWhenOtherWebSecurityAdapterPresent() {
this.contextRunner
.withUserConfiguration(TestWebSecurityConfigurerConfig.class, OAuth2WebSecurityConfiguration.class)
.run((context) -> {
@ -125,6 +127,28 @@ class OAuth2WebSecurityConfigurationTests {
});
}
@Test
void securityConfigurerBacksOffBacksOffWhenOtherSecurityFilterChainBeanPresent() {
this.contextRunner
.withUserConfiguration(TestSecurityFilterChainConfig.class, OAuth2WebSecurityConfiguration.class)
.run((context) -> {
assertThat(getFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty();
assertThat(getFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty();
assertThat(context).getBean(OAuth2AuthorizedClientService.class).isNotNull();
});
}
@Test
void securityConfigurerBacksOffConditionalOnSecurityFilterChainClass() {
this.contextRunner
.withUserConfiguration(ClientRegistrationRepositoryConfiguration.class,
OAuth2WebSecurityConfiguration.class)
.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class)).run((context) -> {
assertThat(getFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty();
assertThat(getFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty();
});
}
@Test
void authorizedClientServiceBeanIsConditionalOnMissingBean() {
this.contextRunner.withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class,
@ -211,6 +235,19 @@ class OAuth2WebSecurityConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@Import(ClientRegistrationRepositoryConfiguration.class)
static class TestSecurityFilterChainConfig {
@Bean
SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
return http.antMatcher("/**").authorizeRequests((authorize) -> authorize.anyRequest().authenticated())
.build();
}
}
@Configuration(proxyBeanMethods = false)
@Import(ClientRegistrationRepositoryConfiguration.class)
static class OAuth2AuthorizedClientServiceConfiguration {

View File

@ -41,6 +41,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
@ -279,6 +280,30 @@ class OAuth2ResourceServerAutoConfigurationTests {
});
}
@Test
void jwtSecurityFilterShouldBeConditionalOnSecurityFilterChainClass() {
this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
.withUserConfiguration(JwtDecoderConfig.class)
.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class)).run((context) -> {
assertThat(context).hasSingleBean(OAuth2ResourceServerAutoConfiguration.class);
assertThat(getBearerTokenFilter(context)).isNull();
});
}
@Test
void opaqueTokenSecurityFilterShouldBeConditionalOnSecurityFilterChainClass() {
this.contextRunner
.withPropertyValues(
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id",
"spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret")
.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class)).run((context) -> {
assertThat(context).hasSingleBean(OAuth2ResourceServerAutoConfiguration.class);
assertThat(getBearerTokenFilter(context)).isNull();
});
}
@Test
void autoConfigurationWhenJwkSetUriAndIntrospectionUriAvailable() {
this.contextRunner
@ -351,6 +376,24 @@ class OAuth2ResourceServerAutoConfigurationTests {
});
}
@Test
void jwtSecurityConfigurerBacksOffWhenSecurityFilterChainBeanIsPresent() {
this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
.withUserConfiguration(JwtDecoderConfig.class, TestSecurityFilterChainConfig.class)
.run((context) -> assertThat(context).hasSingleBean(SecurityFilterChain.class));
}
@Test
void opaqueTokenSecurityConfigurerBacksOffWhenSecurityFilterChainBeanIsPresent() {
this.contextRunner.withUserConfiguration(TestSecurityFilterChainConfig.class)
.withPropertyValues(
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id",
"spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret")
.run((context) -> assertThat(context).hasSingleBean(SecurityFilterChain.class));
}
private Filter getBearerTokenFilter(AssertableWebApplicationContext context) {
FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
List<SecurityFilterChain> filterChains = filterChain.getFilterChains();
@ -427,4 +470,16 @@ class OAuth2ResourceServerAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
static class TestSecurityFilterChainConfig {
@Bean
SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
return http.antMatcher("/**").authorizeRequests((authorize) -> authorize.anyRequest().authenticated())
.build();
}
}
}

View File

@ -31,6 +31,7 @@ import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
@ -47,7 +48,7 @@ import static org.mockito.Mockito.mock;
*
* @author Madhura Bhave
*/
public class Saml2RelyingPartyAutoConfigurationTests {
class Saml2RelyingPartyAutoConfigurationTests {
private static final String PREFIX = "spring.security.saml2.relyingparty.registration";
@ -133,6 +134,20 @@ public class Saml2RelyingPartyAutoConfigurationTests {
.run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse());
}
@Test
void samlLoginShouldBackOffWhenASecurityFilterChainBeanIsPresent() {
this.contextRunner.withUserConfiguration(TestSecurityFilterChainConfig.class)
.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse());
}
@Test
void samlLoginShouldShouldBeConditionalOnSecurityWebFilterClass() {
this.contextRunner.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class))
.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(context).doesNotHaveBean(SecurityFilterChain.class));
}
private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests) {
return new String[] { PREFIX
+ ".foo.identityprovider.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
@ -183,4 +198,15 @@ public class Saml2RelyingPartyAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class TestSecurityFilterChainConfig {
@Bean
SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
return http.antMatcher("/**").authorizeRequests((authorize) -> authorize.anyRequest().authenticated())
.build();
}
}
}

View File

@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoCon
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.test.City;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
@ -37,12 +38,14 @@ import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
@ -67,6 +70,28 @@ class SecurityAutoConfigurationTests {
});
}
@Test
void enableWebSecurityIsConditionalOnClass() {
this.contextRunner.withClassLoader(new FilteredClassLoader("org.springframework.security.config"))
.run((context) -> assertThat(context).doesNotHaveBean("springSecurityFilterChain"));
}
@Test
void securityConfigurerBacksOffWhenOtherSecurityFilterChainBeanPresent() {
this.contextRunner.withUserConfiguration(TestSecurityFilterChainConfig.class).run((context) -> {
assertThat(context.getBeansOfType(SecurityFilterChain.class).size()).isEqualTo(1);
assertThat(context.containsBean("testSecurityFilterChain")).isTrue();
});
}
@Test
void securityConfigurerBacksOffWhenOtherWebSecurityAdapterBeanPresent() {
this.contextRunner.withUserConfiguration(WebSecurity.class).run((context) -> {
assertThat(context.getBeansOfType(WebSecurityConfigurerAdapter.class).size()).isEqualTo(1);
assertThat(context.containsBean("securityAutoConfigurationTests.WebSecurity")).isTrue();
});
}
@Test
void testDefaultFilterOrderWithSecurityAdapter() {
this.contextRunner
@ -207,4 +232,16 @@ class SecurityAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class TestSecurityFilterChainConfig {
@Bean
SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
return http.antMatcher("/**").authorizeRequests((authorize) -> authorize.anyRequest().authenticated())
.build();
}
}
}

View File

@ -23,6 +23,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
@ -31,7 +32,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
*
* @author Madhura Bhave
*/
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnClass({ SecurityFilterChain.class, WebSecurityConfigurerAdapter.class })
@Configuration(proxyBeanMethods = false)
class RemoteDevtoolsSecurityConfiguration {

View File

@ -2265,7 +2265,7 @@ For more about Spring Security, see the {spring-security}[Spring Security projec
[[howto-switch-off-spring-boot-security-configuration]]
=== Switch off the Spring Boot Security Configuration
If you define a `@Configuration` with a `WebSecurityConfigurerAdapter` in your application, it switches off the default webapp security settings in Spring Boot.
If you define a `@Configuration` with a `WebSecurityConfigurerAdapter` or a `SecurityFilterChain` bean in your application, it switches off the default webapp security settings in Spring Boot.
[[howto-change-the-user-details-service-and-add-user-accounts]]

View File

@ -3656,8 +3656,8 @@ You can register multiple relying parties under the `spring.security.saml2.relyi
For security purposes, all actuators other than `/health` and `/info` are disabled by default.
The configprop:management.endpoints.web.exposure.include[] property can be used to enable the actuators.
If Spring Security is on the classpath and no other WebSecurityConfigurerAdapter is present, all actuators other than `/health` and `/info` are secured by Spring Boot auto-configuration.
If you define a custom `WebSecurityConfigurerAdapter`, Spring Boot auto-configuration will back off and you will be in full control of actuator access rules.
If Spring Security is on the classpath and no other `WebSecurityConfigurerAdapter` or `SecurityFilterChain` bean is present, all actuators other than `/health` and `/info` are secured by Spring Boot auto-configuration.
If you define a custom `WebSecurityConfigurerAdapter` or `SecurityFilterChain` bean, Spring Boot auto-configuration will back off and you will be in full control of actuator access rules.
NOTE: Before setting the `management.endpoints.web.exposure.include`, ensure that the exposed actuators do not contain sensitive information and/or are secured by placing them behind a firewall or by something like Spring Security.

View File

@ -52,7 +52,8 @@ public final class WebMvcTypeExcludeFilter extends StandardAnnotationCustomizabl
private static final Class<?>[] NO_CONTROLLERS = {};
private static final String[] OPTIONAL_INCLUDES = {
"org.springframework.security.config.annotation.web.WebSecurityConfigurer" };
"org.springframework.security.config.annotation.web.WebSecurityConfigurer",
"org.springframework.security.web.SecurityFilterChain" };
private static final Set<Class<?>> DEFAULT_INCLUDES;

View File

@ -28,6 +28,7 @@ import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@ -57,6 +58,7 @@ class WebMvcTypeExcludeFilterTests {
assertThat(excludes(filter, ExampleService.class)).isTrue();
assertThat(excludes(filter, ExampleRepository.class)).isTrue();
assertThat(excludes(filter, ExampleWebSecurityConfigurer.class)).isFalse();
assertThat(excludes(filter, SecurityFilterChain.class)).isFalse();
assertThat(excludes(filter, ExampleHandlerInterceptor.class)).isFalse();
assertThat(excludes(filter, ExampleModule.class)).isFalse();
}
@ -72,6 +74,7 @@ class WebMvcTypeExcludeFilterTests {
assertThat(excludes(filter, ExampleService.class)).isTrue();
assertThat(excludes(filter, ExampleRepository.class)).isTrue();
assertThat(excludes(filter, ExampleWebSecurityConfigurer.class)).isFalse();
assertThat(excludes(filter, SecurityFilterChain.class)).isFalse();
assertThat(excludes(filter, ExampleHandlerInterceptor.class)).isFalse();
assertThat(excludes(filter, ExampleModule.class)).isFalse();
}
@ -87,6 +90,7 @@ class WebMvcTypeExcludeFilterTests {
assertThat(excludes(filter, ExampleService.class)).isTrue();
assertThat(excludes(filter, ExampleRepository.class)).isTrue();
assertThat(excludes(filter, ExampleWebSecurityConfigurer.class)).isTrue();
assertThat(excludes(filter, SecurityFilterChain.class)).isTrue();
assertThat(excludes(filter, ExampleHandlerInterceptor.class)).isTrue();
assertThat(excludes(filter, ExampleModule.class)).isTrue();
}
@ -116,6 +120,7 @@ class WebMvcTypeExcludeFilterTests {
assertThat(excludes(filter, ExampleService.class)).isTrue();
assertThat(excludes(filter, ExampleRepository.class)).isTrue();
assertThat(excludes(filter, ExampleWebSecurityConfigurer.class)).isFalse();
assertThat(excludes(filter, SecurityFilterChain.class)).isFalse();
assertThat(excludes(filter, ExampleHandlerInterceptor.class)).isFalse();
assertThat(excludes(filter, ExampleModule.class)).isFalse();
}