From 7e3e821db1021b7cbc8f658c5615810bb34fb0a6 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Fri, 13 Jun 2014 15:22:06 -0500 Subject: [PATCH] SEC-2593: Support stateless mode in Spring Security Test --- .../SecurityMockMvcRequestPostProcessors.java | 46 +++++++- .../test/web/support/WebTestUtils.java | 19 ++++ ...rocessorsAuthenticationStatelessTests.java | 100 ++++++++++++++++++ 3 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessorsAuthenticationStatelessTests.java diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java index f783a5db36..a0649b8674 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java @@ -233,12 +233,17 @@ public final class SecurityMockMvcRequestPostProcessors { */ final void save(SecurityContext securityContext, HttpServletRequest request) { + SecurityContextRepository securityContextRepository = WebTestUtils.getSecurityContextRepository(request); + boolean isTestRepository = securityContextRepository instanceof TestSecurityContextRepository; + if(!isTestRepository) { + securityContextRepository = new TestSecurityContextRepository(securityContextRepository); + WebTestUtils.setSecurityContextRepository(request, securityContextRepository); + } + HttpServletResponse response = new MockHttpServletResponse(); HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder( request, response); - SecurityContextRepository securityContextRepository = WebTestUtils - .getSecurityContextRepository(request); securityContextRepository.loadContext(requestResponseHolder); request = requestResponseHolder.getRequest(); @@ -247,6 +252,43 @@ public final class SecurityMockMvcRequestPostProcessors { securityContextRepository.saveContext(securityContext, request, response); } + + /** + * Used to wrap the SecurityContextRepository to provide support for testing in stateless mode + */ + private static class TestSecurityContextRepository implements SecurityContextRepository { + private final String ATTR_NAME = TestSecurityContextRepository.class.getName().concat(".REPO"); + + private final SecurityContextRepository delegate; + + private TestSecurityContextRepository(SecurityContextRepository delegate) { + this.delegate = delegate; + } + + @Override + public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { + SecurityContext result = getContext(requestResponseHolder.getRequest()); + // always load from the delegate to ensure the request/response in the holder are updated + // remember the SecurityContextRepository is used in many different locations + SecurityContext delegateResult = delegate.loadContext(requestResponseHolder); + return result == null ? delegateResult : result; + } + + @Override + public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { + request.setAttribute(ATTR_NAME, context); + delegate.saveContext(context, request, response); + } + + @Override + public boolean containsContext(HttpServletRequest request) { + return getContext(request) != null || delegate.containsContext(request); + } + + private SecurityContext getContext(HttpServletRequest request) { + return (SecurityContext) request.getAttribute(ATTR_NAME); + } + } } /** diff --git a/test/src/main/java/org/springframework/security/test/web/support/WebTestUtils.java b/test/src/main/java/org/springframework/security/test/web/support/WebTestUtils.java index a98c06a2b5..5ce7c90969 100644 --- a/test/src/main/java/org/springframework/security/test/web/support/WebTestUtils.java +++ b/test/src/main/java/org/springframework/security/test/web/support/WebTestUtils.java @@ -61,6 +61,25 @@ public abstract class WebTestUtils { return (SecurityContextRepository) ReflectionTestUtils.getField(filter, "repo"); } + /** + * Sets the {@link SecurityContextRepository} for the specified + * {@link HttpServletRequest}. + * + * @param request + * the {@link HttpServletRequest} to obtain the + * {@link SecurityContextRepository} + * @param securityContextRepository + * the {@link SecurityContextRepository} to set + * @return the {@link SecurityContextRepository} for the specified + * {@link HttpServletRequest} + */ + public static void setSecurityContextRepository(HttpServletRequest request, SecurityContextRepository securityContextRepository) { + SecurityContextPersistenceFilter filter = findFilter(request, SecurityContextPersistenceFilter.class); + if(filter != null) { + ReflectionTestUtils.setField(filter, "repo", securityContextRepository); + } + } + /** * Gets the {@link CsrfTokenRepository} for the specified * {@link HttpServletRequest}. If one is not found, the default diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessorsAuthenticationStatelessTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessorsAuthenticationStatelessTests.java new file mode 100644 index 0000000000..395528af19 --- /dev/null +++ b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessorsAuthenticationStatelessTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.test.web.servlet.request; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +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.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import javax.servlet.Filter; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class SecurityMockMvcRequestPostProcessorsAuthenticationStatelessTests { + + @Autowired + private WebApplicationContext context; + + @Autowired + private Filter springSecurityFilterChain; + + private MockMvc mvc; + + @Before + public void setup() { + mvc = MockMvcBuilders + .webAppContextSetup(context) + .addFilters(springSecurityFilterChain) + .build(); + } + + @Test + public void userRequestPostProcessorWorksWithStateless() throws Exception { + mvc + .perform(get("/").with(user("user"))) + .andExpect(status().is2xxSuccessful()); + } + + @Configuration + @EnableWebMvcSecurity + @EnableWebMvc + static class Config extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + super.configure(http); + + http + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication(); + } + + @RestController + static class Controller { + @RequestMapping + public String hello() { + return "Hello"; + } + } + } +} \ No newline at end of file