Remove need for WebSecurityConfigurerAdapter

Closes gh-8804
This commit is contained in:
Eleftheria Stein
2020-07-03 11:27:48 +02:00
committed by Eleftheria Stein-Kousathana
parent 5061ae9e79
commit aeafe04260
8 changed files with 561 additions and 10 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -29,6 +29,7 @@ import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.debug.DebugFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
@@ -123,6 +124,32 @@ public class EnableWebSecurityTests {
}
}
@Test
public void securityFilterChainWhenEnableWebMvcThenAuthenticationPrincipalResolvable() throws Exception {
this.spring.register(SecurityFilterChainAuthenticationPrincipalConfig.class).autowire();
this.mockMvc.perform(get("/").with(authentication(new TestingAuthenticationToken("user1", "password"))))
.andExpect(content().string("user1"));
}
@EnableWebSecurity
@EnableWebMvc
static class SecurityFilterChainAuthenticationPrincipalConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.build();
}
@RestController
static class AuthController {
@GetMapping("/")
String principal(@AuthenticationPrincipal String principal) {
return principal;
}
}
}
@Test
public void enableWebSecurityWhenNoConfigurationAnnotationThenBeanProxyingEnabled() {
this.spring.register(BeanProxyEnabledByDefaultConfig.class).autowire();

View File

@@ -0,0 +1,248 @@
/*
* Copyright 2002-2020 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.security.config.annotation.web.configuration;
import com.google.common.net.HttpHeaders;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link HttpSecurityConfiguration}.
*
* @author Eleftheria Stein
*/
public class HttpSecurityConfigurationTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
private MockMvc mockMvc;
@Test
public void postWhenDefaultFilterChainBeanThenRespondsWithForbidden() throws Exception {
this.spring.register(DefaultWithFilterChainConfig.class)
.autowire();
this.mockMvc.perform(post("/"))
.andExpect(status().isForbidden());
}
@Test
public void getWhenDefaultFilterChainBeanThenDefaultHeadersInResponse() throws Exception {
this.spring.register(DefaultWithFilterChainConfig.class).autowire();
MvcResult mvcResult = this.mockMvc.perform(get("/").secure(true))
.andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff"))
.andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name()))
.andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains"))
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate"))
.andExpect(header().string(HttpHeaders.EXPIRES, "0"))
.andExpect(header().string(HttpHeaders.PRAGMA, "no-cache"))
.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block"))
.andReturn();
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder(
HttpHeaders.X_CONTENT_TYPE_OPTIONS, HttpHeaders.X_FRAME_OPTIONS, HttpHeaders.STRICT_TRANSPORT_SECURITY,
HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA, HttpHeaders.X_XSS_PROTECTION);
}
@Test
public void logoutWhenDefaultFilterChainBeanThenCreatesDefaultLogoutEndpoint() throws Exception {
this.spring.register(DefaultWithFilterChainConfig.class).autowire();
this.mockMvc.perform(post("/logout").with(csrf()))
.andExpect(redirectedUrl("/login?logout"));
}
@Test
public void loadConfigWhenDefaultConfigThenWebAsyncManagerIntegrationFilterAdded() throws Exception {
this.spring.register(DefaultWithFilterChainConfig.class, NameController.class).autowire();
MvcResult mvcResult = this.mockMvc.perform(get("/name").with(user("Bob")))
.andExpect(request().asyncStarted())
.andReturn();
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
.andExpect(content().string("Bob"));
}
@RestController
static class NameController {
@GetMapping("/name")
public Callable<String> name() {
return () -> SecurityContextHolder.getContext().getAuthentication().getName();
}
}
@EnableWebSecurity
static class DefaultWithFilterChainConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.build();
}
}
@Test
public void getWhenDefaultFilterChainBeanThenAnonymousPermitted() throws Exception {
this.spring.register(AuthorizeRequestsConfig.class, UserDetailsConfig.class, BaseController.class).autowire();
this.mockMvc.perform(get("/"))
.andExpect(status().isOk());
}
@EnableWebSecurity
static class AuthorizeRequestsConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests(authorize -> authorize
.anyRequest().permitAll()
)
.build();
}
}
@Test
public void authenticateWhenDefaultFilterChainBeanThenSessionIdChanges() throws Exception {
this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class).autowire();
MockHttpSession session = new MockHttpSession();
String sessionId = session.getId();
MvcResult result =
this.mockMvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.session(session)
.with(csrf()))
.andReturn();
assertThat(result.getRequest().getSession(false).getId()).isNotEqualTo(sessionId);
}
@Test
public void authenticateWhenDefaultFilterChainBeanThenRedirectsToSavedRequest() throws Exception {
this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mockMvc.perform(get("/messages"))
.andReturn().getRequest().getSession();
this.mockMvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.session(session)
.with(csrf()))
.andExpect(redirectedUrl("http://localhost/messages"));
}
@Test
public void authenticateWhenDefaultFilterChainBeanThenRolePrefixIsSet() throws Exception {
this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class, UserController.class).autowire();
this.mockMvc.perform(get("/user")
.with(authentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"))))
.andExpect(status().isOk());
}
@Test
public void loginWhenUsingDefaultsThenDefaultLoginPageGenerated() throws Exception {
this.spring.register(SecurityEnabledConfig.class).autowire();
this.mockMvc.perform(get("/login"))
.andExpect(status().isOk());
}
@EnableWebSecurity
static class SecurityEnabledConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults())
.build();
}
}
@Configuration
static class UserDetailsConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
@RestController
static class BaseController {
@GetMapping("/")
public void index() {
}
}
@RestController
static class UserController {
@GetMapping("/user")
public void user(HttpServletRequest request) {
if (!request.isUserInRole("USER")) {
throw new AccessDeniedException("This resource is only available to users");
}
}
}
}

View File

@@ -170,6 +170,76 @@ public class WebSecurityConfigurationTests {
}
}
@Test
public void loadConfigWhenSecurityFilterChainsHaveOrderThenFilterChainsOrdered() {
this.spring.register(SortedSecurityFilterChainConfig.class).autowire();
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
assertThat(filterChains).hasSize(4);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
request.setServletPath("/role1/**");
assertThat(filterChains.get(0).matches(request)).isTrue();
request.setServletPath("/role2/**");
assertThat(filterChains.get(1).matches(request)).isTrue();
request.setServletPath("/role3/**");
assertThat(filterChains.get(2).matches(request)).isTrue();
request.setServletPath("/**");
assertThat(filterChains.get(3).matches(request)).isTrue();
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class SortedSecurityFilterChainConfig {
@Order(1)
@Bean
SecurityFilterChain filterChain1(HttpSecurity http) throws Exception {
return http
.antMatcher("/role1/**")
.authorizeRequests(authorize -> authorize
.anyRequest().hasRole("1")
)
.build();
}
@Order(2)
@Bean
SecurityFilterChain filterChain2(HttpSecurity http) throws Exception {
return http
.antMatcher("/role2/**")
.authorizeRequests(authorize -> authorize
.anyRequest().hasRole("2")
)
.build();
}
@Order(3)
@Bean
SecurityFilterChain filterChain3(HttpSecurity http) throws Exception {
return http
.antMatcher("/role3/**")
.authorizeRequests(authorize -> authorize
.anyRequest().hasRole("3")
)
.build();
}
@Bean
SecurityFilterChain filterChain4(HttpSecurity http) throws Exception {
return http
.authorizeRequests(authorize -> authorize
.anyRequest().hasRole("4")
)
.build();
}
}
@Test
public void loadConfigWhenWebSecurityConfigurersHaveSameOrderThenThrowBeanCreationException() {
Throwable thrown = catchThrowable(() -> this.spring.register(DuplicateOrderConfig.class).autowire());
@@ -372,6 +442,26 @@ public class WebSecurityConfigurationTests {
}
}
@Test
public void loadConfigWhenSecurityFilterChainBeanThenDefaultWebInvocationPrivilegeEvaluatorIsRegistered() {
this.spring.register(AuthorizeRequestsFilterChainConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
}
@EnableWebSecurity
static class AuthorizeRequestsFilterChainConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.build();
}
}
// SEC-2303
@Test
public void loadConfigWhenDefaultSecurityExpressionHandlerThenBeanResolverSet() throws Exception {
@@ -499,4 +589,42 @@ public class WebSecurityConfigurationTests {
}
}
}
@Test
public void loadConfigWhenBothAdapterAndFilterChainConfiguredThenException() {
Throwable thrown = catchThrowable(() -> this.spring.register(AdapterAndFilterChainConfig.class).autowire());
assertThat(thrown).isInstanceOf(BeanCreationException.class)
.hasRootCauseExactlyInstanceOf(IllegalStateException.class)
.hasMessageContaining("Found WebSecurityConfigurerAdapter as well as SecurityFilterChain.");
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class AdapterAndFilterChainConfig {
@Order(1)
@Configuration
static class WebConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/config/**")
.authorizeRequests(authorize -> authorize
.anyRequest().permitAll()
);
}
}
@Order(2)
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.antMatcher("/filter/**")
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.build();
}
}
}