SessionManagementConfigTests groovy->java

Issue: gh-4939
This commit is contained in:
Josh Cummings
2018-07-13 14:34:40 -06:00
parent 191a4760f9
commit f0f678d61e
20 changed files with 1340 additions and 430 deletions

View File

@@ -0,0 +1,667 @@
/*
* Copyright 2002-2018 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.config.http;
import java.io.IOException;
import java.security.Principal;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.http.HttpStatus;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.util.FieldUtils;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
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.cookie;
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.status;
/**
* Tests session-related functionality for the <http> namespace element and <session-management>
*
* @author Luke Taylor
* @author Rob Winch
* @author Josh Cummings
*/
public class SessionManagementConfigTests {
private static final String CONFIG_LOCATION_PREFIX =
"classpath:org/springframework/security/config/http/SessionManagementConfigTests";
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mvc;
@Test
public void requestWhenCreateSessionAlwaysThenAlwaysCreatesSession()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionAlways")).autowire();
MockHttpServletRequest request = get("/").buildRequest(this.servletContext());
MockHttpServletResponse response = request(request, this.spring.getContext());
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK);
assertThat(request.getSession(false)).isNotNull();
}
@Test
public void requestWhenCreateSessionIsSetToNeverThenDoesNotCreateSessionOnLoginChallenge()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionNever")).autowire();
MockHttpServletRequest request = get("/auth").buildRequest(this.servletContext());
MockHttpServletResponse response = request(request, this.spring.getContext());
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
assertThat(request.getSession(false)).isNull();
}
@Test
public void requestWhenCreateSessionIsSetToNeverThenDoesNotCreateSessionOnLogin()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionNever")).autowire();
MockHttpServletRequest request = post("/login")
.param("username", "user")
.param("password", "password")
.buildRequest(this.servletContext());
request = csrf().postProcessRequest(request);
MockHttpServletResponse response = request(request, this.spring.getContext());
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
assertThat(request.getSession(false)).isNull();
}
@Test
public void requestWhenCreateSessionIsSetToNeverThenUsesExistingSession()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionNever")).autowire();
MockHttpServletRequest request = post("/login")
.param("username", "user")
.param("password", "password")
.buildRequest(this.servletContext());
request = csrf().postProcessRequest(request);
MockHttpSession session = new MockHttpSession();
request.setSession(session);
MockHttpServletResponse response = request(request, this.spring.getContext());
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
assertThat(request.getSession(false)).isNotNull();
assertThat(request.getSession(false).getAttribute(SPRING_SECURITY_CONTEXT_KEY))
.isNotNull();
}
@Test
public void requestWhenCreateSessionIsSetToStatelessThenDoesNotCreateSessionOnLoginChallenge()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionStateless")).autowire();
this.mvc.perform(get("/auth"))
.andExpect(status().isFound())
.andExpect(session().exists(false));
}
@Test
public void requestWhenCreateSessionIsSetToStatelessThenDoesNotCreateSessionOnLogin()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionStateless")).autowire();
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.with(csrf()))
.andExpect(status().isFound())
.andExpect(session().exists(false));
}
@Test
public void requestWhenCreateSessionIsSetToStatelessThenIgnoresExistingSession()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionStateless")).autowire();
MvcResult result =
this.mvc.perform(post("/login")
.param("username", "user")
.param("password", "password")
.session(new MockHttpSession())
.with(csrf()))
.andExpect(status().isFound())
.andExpect(session())
.andReturn();
assertThat(result.getRequest().getSession(false).getAttribute(SPRING_SECURITY_CONTEXT_KEY))
.isNull();
}
@Test
public void requestWhenCreateSessionIsSetToIfRequiredThenDoesNotCreateSessionOnPublicInvocation()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionIfRequired")).autowire();
ServletContext servletContext = this.mvc.getDispatcherServlet().getServletContext();
MockHttpServletRequest request = get("/").buildRequest(servletContext);
MockHttpServletResponse response = request(request, this.spring.getContext());
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK);
assertThat(request.getSession(false)).isNull();
}
@Test
public void requestWhenCreateSessionIsSetToIfRequiredThenCreatesSessionOnLoginChallenge()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionIfRequired")).autowire();
ServletContext servletContext = this.mvc.getDispatcherServlet().getServletContext();
MockHttpServletRequest request = get("/auth").buildRequest(servletContext);
MockHttpServletResponse response = request(request, this.spring.getContext());
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
assertThat(request.getSession(false)).isNotNull();
}
@Test
public void requestWhenCreateSessionIsSetToIfRequiredThenCreatesSessionOnLogin()
throws Exception {
this.spring.configLocations(this.xml("CreateSessionIfRequired")).autowire();
ServletContext servletContext = this.mvc.getDispatcherServlet().getServletContext();
MockHttpServletRequest request = post("/login")
.param("username", "user")
.param("password", "password")
.buildRequest(servletContext);
request = csrf().postProcessRequest(request);
MockHttpServletResponse response = request(request, this.spring.getContext());
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
assertThat(request.getSession(false)).isNotNull();
}
/**
* SEC-1208
*/
@Test
public void requestWhenRejectingUserBasedOnMaxSessionsExceededThenDoesNotCreateSession()
throws Exception {
this.spring.configLocations(this.xml("Sec1208")).autowire();
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(status().isOk())
.andExpect(session());
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(status().isUnauthorized())
.andExpect(session().exists(false));
}
/**
* SEC-2137
*/
@Test
public void requestWhenSessionFixationProtectionDisabledAndConcurrencyControlEnabledThenSessionNotInvalidated()
throws Exception {
this.spring.configLocations(this.xml("Sec2137")).autowire();
MockHttpSession session = new MockHttpSession();
this.mvc.perform(get("/auth")
.session(session)
.with(httpBasic("user", "password")))
.andExpect(status().isOk())
.andExpect(session().id(session.getId()));
}
@Test
public void autowireWhenExportingSessionRegistryBeanThenAvailableForWiring() {
this.spring.configLocations(this.xml("ConcurrencyControlSessionRegistryAlias")).autowire();
this.sessionRegistryIsValid();
}
@Test
public void requestWhenExpiredUrlIsSetThenInvalidatesSessionAndRedirects()
throws Exception {
this.spring.configLocations(this.xml("ConcurrencyControlExpiredUrl")).autowire();
this.mvc.perform(get("/auth")
.session(this.expiredSession())
.with(httpBasic("user", "password")))
.andExpect(redirectedUrl("/expired"))
.andExpect(session().exists(false));
}
@Test
public void requestWhenConcurrencyControlAndCustomLogoutHandlersAreSetThenAllAreInvokedWhenSessionExpires()
throws Exception {
this.spring.configLocations(this.xml("ConcurrencyControlLogoutAndRememberMeHandlers")).autowire();
this.mvc.perform(get("/auth")
.session(this.expiredSession())
.with(httpBasic("user", "password")))
.andExpect(status().isOk())
.andExpect(cookie().maxAge("testCookie", 0))
.andExpect(cookie().exists("rememberMeCookie"))
.andExpect(session().valid(true));
}
@Test
public void requestWhenConcurrencyControlAndRememberMeAreSetThenInvokedWhenSessionExpires()
throws Exception {
this.spring.configLocations(this.xml("ConcurrencyControlRememberMeHandler")).autowire();
this.mvc.perform(get("/auth")
.session(this.expiredSession())
.with(httpBasic("user", "password")))
.andExpect(status().isOk())
.andExpect(cookie().exists("rememberMeCookie"))
.andExpect(session().exists(false));
}
/**
* SEC-2057
*/
@Test
public void autowireWhenConcurrencyControlIsSetThenLogoutHandlersGetAuthenticationObject()
throws Exception {
this.spring.configLocations(this.xml("ConcurrencyControlCustomLogoutHandler")).autowire();
MvcResult result =
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(session())
.andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false);
SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class);
sessionRegistry.getSessionInformation(session.getId()).expireNow();
this.mvc.perform(get("/auth")
.session(session))
.andExpect(header().string("X-Username", "user"));
}
@Test
public void requestWhenConcurrencyControlIsSetThenDefaultsToResponseBodyExpirationResponse()
throws Exception {
this.spring.configLocations(this.xml("ConcurrencyControlSessionRegistryAlias")).autowire();
this.mvc.perform(get("/auth")
.session(this.expiredSession())
.with(httpBasic("user", "password")))
.andExpect(content().string("This session has been expired (possibly due to multiple concurrent "
+ "logins being attempted as the same user)."));
}
@Test
public void requestWhenCustomSessionAuthenticationStrategyThenInvokesOnAuthentication()
throws Exception {
this.spring.configLocations(this.xml("SessionAuthenticationStrategyRef")).autowire();
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(status().isIAmATeapot());
}
@Test
public void autowireWhenSessionRegistryRefIsSetThenAvailableForWiring() {
this.spring.configLocations(this.xml("ConcurrencyControlSessionRegistryRef")).autowire();
this.sessionRegistryIsValid();
}
@Test
public void requestWhenMaxSessionsIsSetThenErrorsWhenExceeded()
throws Exception {
this.spring.configLocations(this.xml("ConcurrencyControlMaxSessions")).autowire();
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(status().isOk());
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(status().isOk());
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(redirectedUrl("/max-exceeded"));
}
@Test
public void autowireWhenSessionFixationProtectionIsNoneAndCsrfDisabledThenSessionManagementFilterIsNotWired() {
this.spring.configLocations(this.xml("NoSessionManagementFilter")).autowire();
assertThat(this.getFilter(SessionManagementFilter.class)).isNull();
}
@Test
public void requestWhenSessionFixationProtectionIsNoneThenSessionNotInvalidated()
throws Exception {
this.spring.configLocations(this.xml("SessionFixationProtectionNone")).autowire();
MockHttpSession session = new MockHttpSession();
String sessionId = session.getId();
this.mvc.perform(get("/auth")
.session(session)
.with(httpBasic("user", "password")))
.andExpect(session().id(sessionId));
}
@Test
public void requestWhenSessionFixationProtectionIsMigrateSessionThenSessionIsReplaced()
throws Exception {
this.spring.configLocations(this.xml("SessionFixationProtectionMigrateSession")).autowire();
MockHttpSession session = new MockHttpSession();
String sessionId = session.getId();
MvcResult result =
this.mvc.perform(get("/auth")
.session(session)
.with(httpBasic("user", "password")))
.andExpect(session())
.andReturn();
assertThat(result.getRequest().getSession(false).getId()).isNotEqualTo(sessionId);
}
@Test
public void requestWhenSessionFixationProtectionIsNoneAndInvalidSessionUrlIsSetThenStillRedirectsOnInvalidSession()
throws Exception {
this.spring.configLocations(this.xml("SessionFixationProtectionNoneWithInvalidSessionUrl")).autowire();
this.mvc.perform(get("/auth")
.with(request -> {
request.setRequestedSessionId("1");
request.setRequestedSessionIdValid(false);
return request;
}))
.andExpect(redirectedUrl("/timeoutUrl"));
}
static class TeapotSessionAuthenticationStrategy implements SessionAuthenticationStrategy {
@Override
public void onAuthentication(
Authentication authentication,
HttpServletRequest request,
HttpServletResponse response) throws SessionAuthenticationException {
response.setStatus(org.springframework.http.HttpStatus.I_AM_A_TEAPOT.value());
}
}
static class CustomRememberMeServices implements RememberMeServices, LogoutHandler {
@Override
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
return null;
}
@Override
public void loginFail(HttpServletRequest request, HttpServletResponse response) {
}
@Override
public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
response.addHeader("X-Username", authentication.getName());
}
}
@RestController
static class BasicController {
@GetMapping("/")
public String ok() {
return "ok";
}
@GetMapping("/auth")
public String auth(Principal principal) {
return principal.getName();
}
}
private void sessionRegistryIsValid() {
SessionRegistry sessionRegistry = this.spring.getContext()
.getBean("sessionRegistry", SessionRegistry.class);
assertThat(sessionRegistry).isNotNull();
assertThat(this.getFilter(ConcurrentSessionFilter.class))
.returns(sessionRegistry, this::extractSessionRegistry);
assertThat(this.getFilter(UsernamePasswordAuthenticationFilter.class))
.returns(sessionRegistry, this::extractSessionRegistry);
// SEC-1143
assertThat(this.getFilter(SessionManagementFilter.class))
.returns(sessionRegistry, this::extractSessionRegistry);
}
private SessionRegistry extractSessionRegistry(ConcurrentSessionFilter filter) {
return getFieldValue(filter, "sessionRegistry");
}
private SessionRegistry extractSessionRegistry(UsernamePasswordAuthenticationFilter filter) {
SessionAuthenticationStrategy strategy = getFieldValue(filter, "sessionStrategy");
List<SessionAuthenticationStrategy> strategies = getFieldValue(strategy, "delegateStrategies");
return getFieldValue(strategies.get(0), "sessionRegistry");
}
private SessionRegistry extractSessionRegistry(SessionManagementFilter filter) {
SessionAuthenticationStrategy strategy = getFieldValue(filter, "sessionAuthenticationStrategy");
List<SessionAuthenticationStrategy> strategies = getFieldValue(strategy, "delegateStrategies");
return getFieldValue(strategies.get(0), "sessionRegistry");
}
private <T> T getFieldValue(Object target, String fieldName) {
try {
return (T) FieldUtils.getFieldValue(target, fieldName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static SessionResultMatcher session() {
return new SessionResultMatcher();
}
private static class SessionResultMatcher implements ResultMatcher {
private String id;
private Boolean valid;
private Boolean exists = true;
public ResultMatcher exists(boolean exists) {
this.exists = exists;
return this;
}
public ResultMatcher valid(boolean valid) {
this.valid = valid;
return this.exists(true);
}
public ResultMatcher id(String id) {
this.id = id;
return this.exists(true);
}
@Override
public void match(MvcResult result) {
if (!this.exists) {
assertThat(result.getRequest().getSession(false)).isNull();
return;
}
assertThat(result.getRequest().getSession(false)).isNotNull();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false);
if (this.valid != null) {
if (this.valid) {
assertThat(session.isInvalid()).isFalse();
} else {
assertThat(session.isInvalid()).isTrue();
}
}
if (this.id != null) {
assertThat(session.getId()).isEqualTo(this.id);
}
}
}
private static MockHttpServletResponse request(
MockHttpServletRequest request,
ApplicationContext context)
throws IOException, ServletException {
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChainProxy proxy = context.getBean(FilterChainProxy.class);
proxy.doFilter(
request,
new EncodeUrlDenyingHttpServletResponseWrapper(response),
(req, resp) -> {});
return response;
}
private static class EncodeUrlDenyingHttpServletResponseWrapper
extends HttpServletResponseWrapper {
public EncodeUrlDenyingHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public String encodeURL(String url) {
throw new RuntimeException("Unexpected invocation of encodeURL");
}
@Override
public String encodeRedirectURL(String url) {
throw new RuntimeException("Unexpected invocation of encodeURL");
}
@Override
public String encodeUrl(String url) {
throw new RuntimeException("Unexpected invocation of encodeURL");
}
@Override
public String encodeRedirectUrl(String url) {
throw new RuntimeException("Unexpected invocation of encodeURL");
}
}
private MockHttpSession expiredSession() {
MockHttpSession session = new MockHttpSession();
SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class);
sessionRegistry.registerNewSession(session.getId(), "user");
sessionRegistry.getSessionInformation(session.getId()).expireNow();
return session;
}
private <T extends Filter> T getFilter(Class<T> filterClass) {
return (T) getFilters().stream()
.filter(filterClass::isInstance)
.findFirst()
.orElse(null);
}
private List<Filter> getFilters() {
FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class);
return proxy.getFilters("/");
}
private ServletContext servletContext() {
WebApplicationContext context = (WebApplicationContext) this.spring.getContext();
return context.getServletContext();
}
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
}