NamespaceSessionManagementTests groovy->java

Issue: gh-4939
This commit is contained in:
Josh Cummings 2019-06-19 13:25:52 -06:00
parent 329999b54a
commit ee8182dceb
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
2 changed files with 471 additions and 229 deletions

View File

@ -1,229 +0,0 @@
/*
* Copyright 2002-2013 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.configurers
import org.springframework.context.ApplicationListener
import org.springframework.context.annotation.Bean
import org.springframework.mock.web.MockHttpSession
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
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.session.SessionRegistry
import org.springframework.security.web.authentication.session.AbstractSessionFixationProtectionStrategy;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
import org.springframework.security.web.authentication.session.SessionFixationProtectionEvent
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy
import org.springframework.security.web.session.ConcurrentSessionFilter
import org.springframework.security.web.session.SessionManagementFilter
import org.springframework.security.web.session.InvalidSessionStrategy
/**
*
* @author Rob Winch
*/
class NamespaceSessionManagementTests extends BaseSpringSpec {
def "http/session-management"() {
when:
loadConfig(SessionManagementConfig)
then:
findSessionAuthenticationStrategy(AbstractSessionFixationProtectionStrategy)
}
@EnableWebSecurity
static class SessionManagementConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// enabled by default
}
}
def "http/session-management custom"() {
setup:
CustomSessionManagementConfig.SR = Mock(SessionRegistry)
when:
loadConfig(CustomSessionManagementConfig)
def concurrentStrategy = findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies[0]
then:
findFilter(SessionManagementFilter).invalidSessionStrategy.destinationUrl == "/invalid-session"
findFilter(SessionManagementFilter).failureHandler.defaultFailureUrl == "/session-auth-error"
concurrentStrategy.maximumSessions == 1
concurrentStrategy.exceptionIfMaximumExceeded
concurrentStrategy.sessionRegistry == CustomSessionManagementConfig.SR
findFilter(ConcurrentSessionFilter).sessionInformationExpiredStrategy.destinationUrl == "/expired-session"
}
@EnableWebSecurity
static class CustomSessionManagementConfig extends WebSecurityConfigurerAdapter {
static SessionRegistry SR
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.invalidSessionUrl("/invalid-session") // session-management@invalid-session-url
.sessionAuthenticationErrorUrl("/session-auth-error") // session-management@session-authentication-error-url
.maximumSessions(1) // session-management/concurrency-control@max-sessions
.maxSessionsPreventsLogin(true) // session-management/concurrency-control@error-if-maximum-exceeded
.expiredUrl("/expired-session") // session-management/concurrency-control@expired-url
.sessionRegistry(SR) // session-management/concurrency-control@session-registry-ref
}
}
// gh-3371
def "http/session-management custom invalidationstrategy"() {
setup:
InvalidSessionStrategyConfig.ISS = Mock(InvalidSessionStrategy)
when:
loadConfig(InvalidSessionStrategyConfig)
then:
findFilter(SessionManagementFilter).invalidSessionStrategy == InvalidSessionStrategyConfig.ISS
}
@EnableWebSecurity
static class InvalidSessionStrategyConfig extends WebSecurityConfigurerAdapter {
static InvalidSessionStrategy ISS
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.invalidSessionStrategy(ISS)
}
}
def "http/session-management refs"() {
setup:
RefsSessionManagementConfig.SAS = Mock(SessionAuthenticationStrategy)
when:
loadConfig(RefsSessionManagementConfig)
then:
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it == RefsSessionManagementConfig.SAS }
}
@EnableWebSecurity
static class RefsSessionManagementConfig extends WebSecurityConfigurerAdapter {
static SessionAuthenticationStrategy SAS
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionAuthenticationStrategy(SAS) // session-management@session-authentication-strategy-ref
}
}
def "http/session-management@session-fixation-protection=none"() {
when:
loadConfig(SFPNoneSessionManagementConfig)
then:
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it instanceof NullAuthenticatedSessionStrategy }
}
@EnableWebSecurity
static class SFPNoneSessionManagementConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy())
}
}
def "http/session-management@session-fixation-protection=migrateSession (default)"() {
when:
loadConfig(SFPMigrateSessionManagementConfig)
then:
if(isChangeSession()) {
findSessionAuthenticationStrategy(ChangeSessionIdAuthenticationStrategy)
} else {
findSessionAuthenticationStrategy(SessionFixationProtectionStrategy).migrateSessionAttributes
}
}
@EnableWebSecurity
static class SFPMigrateSessionManagementConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
}
}
def "SEC-2913: Default JavaConfig session fixation AuthenticationStrategy has NullEventPublisher"() {
setup:
loadConfig(SFPPostProcessedConfig)
when:
findSessionAuthenticationStrategy(AbstractSessionFixationProtectionStrategy).onSessionChange("id", new MockHttpSession(), new TestingAuthenticationToken("u","p","ROLE_USER"))
then:
context.getBean(MockEventListener).events
}
@EnableWebSecurity
static class SFPPostProcessedConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
}
@Bean
public MockEventListener eventListener() {
new MockEventListener()
}
}
def "http/session-management@session-fixation-protection=newSession"() {
when:
loadConfig(SFPNewSessionSessionManagementConfig)
then:
!findSessionAuthenticationStrategy(SessionFixationProtectionStrategy).migrateSessionAttributes
}
def findSessionAuthenticationStrategy(def c) {
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { c.isAssignableFrom(it.class) }
}
@EnableWebSecurity
static class SFPNewSessionSessionManagementConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionFixation()
.newSession()
}
}
static class MockEventListener implements ApplicationListener<SessionFixationProtectionEvent> {
List<SessionFixationProtectionEvent> events = []
public void onApplicationEvent(SessionFixationProtectionEvent event) {
events.add(event)
}
}
boolean isChangeSession() {
try {
new ChangeSessionIdAuthenticationStrategy()
return true
} catch(Exception e) {}
return false
}
}

View File

@ -0,0 +1,471 @@
/*
* Copyright 2002-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.security.config.annotation.web.configurers;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
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.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionEvent;
import org.springframework.security.web.session.InvalidSessionStrategy;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
*
* @author Rob Winch
* @author Josh Cummings
*/
public class NamespaceSessionManagementTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mvc;
@Test
public void authenticateWhenDefaultSessionManagementThenMatchesNamespace() throws Exception {
this.spring.register
(SessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class).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);
}
@EnableWebSecurity
static class SessionManagementConfig extends WebSecurityConfigurerAdapter {
}
@Test
public void authenticateWhenUsingInvalidSessionUrlThenMatchesNamespace() throws Exception {
this.spring.register(CustomSessionManagementConfig.class).autowire();
this.mvc.perform(get("/auth")
.with(request -> {
request.setRequestedSessionIdValid(false);
request.setRequestedSessionId("id");
return request;
}))
.andExpect(redirectedUrl("/invalid-session"));
}
@Test
public void authenticateWhenUsingExpiredUrlThenMatchesNamespace() throws Exception {
this.spring.register(CustomSessionManagementConfig.class).autowire();
MockHttpSession session = new MockHttpSession();
SessionInformation sessionInformation = new SessionInformation(new Object(), session.getId(), new Date(0));
sessionInformation.expireNow();
SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class);
when(sessionRegistry.getSessionInformation(session.getId())).thenReturn(sessionInformation);
this.mvc.perform(get("/auth").session(session))
.andExpect(redirectedUrl("/expired-session"));
}
@Test
public void authenticateWhenUsingMaxSessionsThenMatchesNamespace() throws Exception {
this.spring.register(CustomSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class).autowire();
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(status().isOk());
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(redirectedUrl("/session-auth-error"));
}
@Test
public void authenticateWhenUsingFailureUrlThenMatchesNamespace() throws Exception {
this.spring.register(CustomSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class).autowire();
MockHttpServletRequest mock = spy(MockHttpServletRequest.class);
mock.setSession(new MockHttpSession());
when(mock.changeSessionId()).thenThrow(SessionAuthenticationException.class);
mock.setMethod("GET");
this.mvc.perform(get("/auth")
.with(request -> mock)
.with(httpBasic("user", "password")))
.andExpect(redirectedUrl("/session-auth-error"));
}
@Test
public void authenticateWhenUsingSessionRegistryThenMatchesNamespace() throws Exception {
this.spring.register(CustomSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class).autowire();
SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class);
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(status().isOk());
verify(sessionRegistry).registerNewSession(any(String.class), any(Object.class));
}
@EnableWebSecurity
static class CustomSessionManagementConfig extends WebSecurityConfigurerAdapter {
SessionRegistry sessionRegistry = spy(SessionRegistryImpl.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.sessionManagement()
.invalidSessionUrl("/invalid-session") // session-management@invalid-session-url
.sessionAuthenticationErrorUrl("/session-auth-error") // session-management@session-authentication-error-url
.maximumSessions(1) // session-management/concurrency-control@max-sessions
.maxSessionsPreventsLogin(true) // session-management/concurrency-control@error-if-maximum-exceeded
.expiredUrl("/expired-session") // session-management/concurrency-control@expired-url
.sessionRegistry(sessionRegistry()); // session-management/concurrency-control@session-registry-ref
}
@Bean
SessionRegistry sessionRegistry() {
return this.sessionRegistry;
}
}
// gh-3371
@Test
public void authenticateWhenUsingCustomInvalidSessionStrategyThenMatchesNamespace() throws Exception {
this.spring.register(InvalidSessionStrategyConfig.class).autowire();
this.mvc.perform(get("/auth")
.with(request -> {
request.setRequestedSessionIdValid(false);
request.setRequestedSessionId("id");
return request;
}))
.andExpect(status().isOk());
verifyBean(InvalidSessionStrategy.class)
.onInvalidSessionDetected(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@EnableWebSecurity
static class InvalidSessionStrategyConfig extends WebSecurityConfigurerAdapter {
InvalidSessionStrategy invalidSessionStrategy = mock(InvalidSessionStrategy.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.invalidSessionStrategy(invalidSessionStrategy());
}
@Bean
InvalidSessionStrategy invalidSessionStrategy() {
return this.invalidSessionStrategy;
}
}
@Test
public void authenticateWhenUsingCustomSessionAuthenticationStrategyThenMatchesNamespace() throws Exception {
this.spring.register(RefsSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class).autowire();
this.mvc.perform(get("/auth")
.with(httpBasic("user", "password")))
.andExpect(status().isOk());
verifyBean(SessionAuthenticationStrategy.class)
.onAuthentication(any(Authentication.class),
any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@EnableWebSecurity
static class RefsSessionManagementConfig extends WebSecurityConfigurerAdapter {
SessionAuthenticationStrategy sessionAuthenticationStrategy =
mock(SessionAuthenticationStrategy.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionAuthenticationStrategy(sessionAuthenticationStrategy()) // session-management@session-authentication-strategy-ref
.and()
.httpBasic();
}
@Bean
SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return this.sessionAuthenticationStrategy;
}
}
@Test
public void authenticateWhenNoSessionFixationProtectionThenMatchesNamespace() throws Exception {
this.spring.register(SFPNoneSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class).autowire();
MockHttpSession givenSession = new MockHttpSession();
String givenSessionId = givenSession.getId();
MockHttpSession resultingSession = (MockHttpSession)
this.mvc.perform(get("/auth")
.session(givenSession)
.with(httpBasic("user", "password")))
.andExpect(status().isOk())
.andReturn().getRequest().getSession(false);
assertThat(givenSessionId).isEqualTo(resultingSession.getId());
}
@EnableWebSecurity
static class SFPNoneSessionManagementConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy())
.and()
.httpBasic();
}
}
@Test
public void authenticateWhenMigrateSessionFixationProtectionThenMatchesNamespace() throws Exception {
this.spring.register(SFPMigrateSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class).autowire();
MockHttpSession givenSession = new MockHttpSession();
String givenSessionId = givenSession.getId();
givenSession.setAttribute("name", "value");
MockHttpSession resultingSession = (MockHttpSession)
this.mvc.perform(get("/auth")
.session(givenSession)
.with(httpBasic("user", "password")))
.andExpect(status().isOk())
.andReturn().getRequest().getSession(false);
assertThat(givenSessionId).isNotEqualTo(resultingSession.getId());
assertThat(resultingSession.getAttribute("name")).isEqualTo("value");
}
@EnableWebSecurity
static class SFPMigrateSessionManagementConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.and()
.httpBasic();
}
}
// SEC-2913
@Test
public void authenticateWhenUsingSessionFixationProtectionThenUsesNonNullEventPublisher() throws Exception {
this.spring.register(SFPPostProcessedConfig.class, UserDetailsServiceConfig.class).autowire();
this.mvc.perform(get("/auth")
.session(new MockHttpSession())
.with(httpBasic("user", "password")))
.andExpect(status().isNotFound());
verifyBean(MockEventListener.class).onApplicationEvent(any(SessionFixationProtectionEvent.class));
}
@EnableWebSecurity
static class SFPPostProcessedConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.and()
.httpBasic();
}
@Bean
public MockEventListener eventListener() {
return spy(new MockEventListener());
}
}
@Test
public void authenticateWhenNewSessionFixationProtectionThenMatchesNamespace() throws Exception {
this.spring.register(SFPNewSessionSessionManagementConfig.class, UserDetailsServiceConfig.class).autowire();
MockHttpSession givenSession = new MockHttpSession();
String givenSessionId = givenSession.getId();
givenSession.setAttribute("name", "value");
MockHttpSession resultingSession = (MockHttpSession)
this.mvc.perform(get("/auth")
.session(givenSession)
.with(httpBasic("user", "password")))
.andExpect(status().isNotFound())
.andReturn().getRequest().getSession(false);
assertThat(givenSessionId).isNotEqualTo(resultingSession.getId());
assertThat(resultingSession.getAttribute("name")).isNull();
}
@EnableWebSecurity
static class SFPNewSessionSessionManagementConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionFixation().newSession()
.and()
.httpBasic();
}
}
private <T> T verifyBean(Class<T> clazz) {
return verify(this.spring.getContext().getBean(clazz));
}
static class MockEventListener implements ApplicationListener<SessionFixationProtectionEvent> {
List<SessionFixationProtectionEvent> events = new ArrayList<>();
public void onApplicationEvent(SessionFixationProtectionEvent event) {
this.events.add(event);
}
}
@Configuration
static class UserDetailsServiceConfig {
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build());
}
}
@RestController
static class BasicController {
@GetMapping("/")
public String ok() {
return "ok";
}
@GetMapping("/auth")
public String auth(Principal principal) {
return principal.getName();
}
}
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);
}
}
}
}