SEC-2137: Allow disabling session fixation and enable concurrency control

This commit is contained in:
Rob Winch
2013-08-08 17:10:21 -05:00
parent 867f02e8ac
commit 13da42ca1b
15 changed files with 970 additions and 65 deletions

View File

@@ -58,12 +58,13 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
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"
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.maximumSessions == 1
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.exceptionIfMaximumExceeded
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.sessionRegistry == CustomSessionManagementConfig.SR
concurrentStrategy.maximumSessions == 1
concurrentStrategy.exceptionIfMaximumExceeded
concurrentStrategy.sessionRegistry == CustomSessionManagementConfig.SR
findFilter(ConcurrentSessionFilter).expiredUrl == "/expired-session"
}
@@ -154,7 +155,8 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionAuthenticationStrategy(new SessionFixationProtectionStrategy(migrateSessionAttributes : false))
.sessionFixation()
.newSession()
}
}
}

View File

@@ -15,16 +15,24 @@
*/
package org.springframework.security.config.annotation.web.configurers
import javax.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.config.annotation.AnyObjectPostProcessor
import org.springframework.security.config.annotation.BaseSpringSpec
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.config.http.SessionCreationPolicy;
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.config.http.SessionCreationPolicy
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.context.NullSecurityContextRepository;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy
import org.springframework.security.web.context.NullSecurityContextRepository
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.context.SecurityContextRepository
import org.springframework.security.web.savedrequest.RequestCache
@@ -110,6 +118,82 @@ class SessionManagementConfigurerTests extends BaseSpringSpec {
}
def 'SEC-2137: disable session fixation and enable concurrency control'() {
setup: "context where session fixation is disabled and concurrency control is enabled"
loadConfig(DisableSessionFixationEnableConcurrencyControlConfig)
String originalSessionId = request.session.id
String credentials = "user:password"
request.addHeader("Authorization", "Basic " + credentials.bytes.encodeBase64())
when: "authenticate"
springSecurityFilterChain.doFilter(request, response, new MockFilterChain())
then: "session invalidate is not called"
request.session.id == originalSessionId
}
@EnableWebSecurity
@Configuration
static class DisableSessionFixationEnableConcurrencyControlConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) {
http
.httpBasic()
.and()
.sessionManagement()
.sessionFixation().none()
.maximumSessions(1)
}
@Override
public void registerAuthentication(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
def 'session fixation and enable concurrency control'() {
setup: "context where session fixation is disabled and concurrency control is enabled"
loadConfig(ConcurrencyControlConfig)
when: "authenticate successfully"
request.servletPath = "/login"
request.method = "POST"
request.setParameter("username", "user");
request.setParameter("password","password")
springSecurityFilterChain.doFilter(request, response, chain)
then: "authentication is sucessful"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
when: "authenticate with the same user"
super.setup()
request.servletPath = "/login"
request.method = "POST"
request.setParameter("username", "user");
request.setParameter("password","password")
springSecurityFilterChain.doFilter(request, response, chain)
then:
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == '/login?error'
}
@EnableWebSecurity
@Configuration
static class ConcurrencyControlConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) {
http
.formLogin()
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
}
@Override
public void registerAuthentication(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
def "sessionManagement ObjectPostProcessor"() {
setup:
AnyObjectPostProcessor opp = Mock()
@@ -122,9 +206,15 @@ class SessionManagementConfigurerTests extends BaseSpringSpec {
.and()
.build()
then: "SessionManagementFilter is registered with LifecycleManager"
then: "SessionManagementFilter is registered with ObjectPostProcessor"
1 * opp.postProcess(_ as SessionManagementFilter) >> {SessionManagementFilter o -> o}
and: "ConcurrentSessionFilter is registered with LifecycleManager"
and: "ConcurrentSessionFilter is registered with ObjectPostProcessor"
1 * opp.postProcess(_ as ConcurrentSessionFilter) >> {ConcurrentSessionFilter o -> o}
and: "ConcurrentSessionControlAuthenticationStrategy is registered with ObjectPostProcessor"
1 * opp.postProcess(_ as ConcurrentSessionControlAuthenticationStrategy) >> {ConcurrentSessionControlAuthenticationStrategy o -> o}
and: "CompositeSessionAuthenticationStrategy is registered with ObjectPostProcessor"
1 * opp.postProcess(_ as CompositeSessionAuthenticationStrategy) >> {CompositeSessionAuthenticationStrategy o -> o}
and: "RegisterSessionAuthenticationStrategy is registered with ObjectPostProcessor"
1 * opp.postProcess(_ as RegisterSessionAuthenticationStrategy) >> {RegisterSessionAuthenticationStrategy o -> o}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* 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.
@@ -15,28 +15,40 @@
*/
package org.springframework.security.config.http
import static org.junit.Assert.assertSame
import static org.mockito.Mockito.*
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.mockito.Mockito
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.context.SecurityContextHolder
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.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.CookieClearingLogoutHandler
import org.springframework.security.web.authentication.logout.LogoutFilter
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy
import org.springframework.security.web.authentication.session.SessionAuthenticationException
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
import org.springframework.security.web.context.NullSecurityContextRepository
import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.session.ConcurrentSessionFilter
import org.springframework.security.web.session.SessionManagementFilter
import static org.junit.Assert.assertSame
/**
* Tests session-related functionality for the <http> namespace element and <session-management>
@@ -93,6 +105,46 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
filter.repo.allowSessionCreation
}
def 'SEC-1208: Session is not created when rejecting user due to max sessions exceeded'() {
setup:
httpCreateSession('never') {
'session-management'() {
'concurrency-control'('max-sessions':1,'error-if-maximum-exceeded':'true')
}
}
createAppContext()
SessionRegistry registry = appContext.getBean(SessionRegistry)
registry.registerNewSession("1", new User("user","password",AuthorityUtils.createAuthorityList("ROLE_USER")))
MockHttpServletRequest request = new MockHttpServletRequest()
MockHttpServletResponse response = new MockHttpServletResponse()
String credentials = "user:password"
request.addHeader("Authorization", "Basic " + credentials.bytes.encodeBase64())
when: "exceed max authentication attempts"
appContext.getBean(FilterChainProxy).doFilter(request, response, new MockFilterChain())
then: "no new session is created"
request.getSession(false) == null
response.status == HttpServletResponse.SC_UNAUTHORIZED
}
def 'SEC-2137: disable session fixation and enable concurrency control'() {
setup: "context where session fixation is disabled and concurrency control is enabled"
httpAutoConfig {
'session-management'('session-fixation-protection':'none') {
'concurrency-control'('max-sessions':'1','error-if-maximum-exceeded':'true')
}
}
createAppContext()
MockHttpServletRequest request = new MockHttpServletRequest()
MockHttpServletResponse response = new MockHttpServletResponse()
String originalSessionId = request.session.id
String credentials = "user:password"
request.addHeader("Authorization", "Basic " + credentials.bytes.encodeBase64())
when: "authenticate"
appContext.getBean(FilterChainProxy).doFilter(request, response, new MockFilterChain())
then: "session invalidate is not called"
request.session.id == originalSessionId
}
def httpCreateSession(String create, Closure c) {
xml.http(['auto-config': 'true', 'create-session': create], c)
}
@@ -219,15 +271,28 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
}
def externalSessionStrategyIsSupported() {
when:
httpAutoConfig {
'session-management'('session-authentication-strategy-ref':'ss')
}
bean('ss', SessionFixationProtectionStrategy.class.name)
createAppContext();
setup:
httpAutoConfig {
'session-management'('session-authentication-strategy-ref':'ss')
}
xml.'b:bean'(id: 'ss', 'class': Mockito.class.name, 'factory-method':'mock') {
'b:constructor-arg'(value : SessionAuthenticationStrategy.class.name)
}
createAppContext()
then:
notThrown(Exception.class)
MockHttpServletRequest request = new MockHttpServletRequest();
request.getSession();
request.setRequestURI("/j_spring_security_check");
request.setMethod("POST");
request.setParameter("j_username", "user");
request.setParameter("j_password", "password");
SessionAuthenticationStrategy sessionAuthStrategy = appContext.getBean('ss',SessionAuthenticationStrategy)
FilterChainProxy springSecurityFilterChain = appContext.getBean(FilterChainProxy)
when:
springSecurityFilterChain.doFilter(request,new MockHttpServletResponse(), new MockFilterChain())
then: "CustomSessionAuthenticationStrategy has seen the request (although REQUEST is a wrapped request)"
verify(sessionAuthStrategy).onAuthentication(any(Authentication), any(HttpServletRequest), any(HttpServletResponse))
}
def externalSessionRegistryBeanIsConfiguredCorrectly() {
@@ -247,10 +312,8 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
Object sessionRegistry = appContext.getBean("sr");
Object sessionRegistryFromConcurrencyFilter = FieldUtils.getFieldValue(
getFilter(ConcurrentSessionFilter.class), "sessionRegistry");
Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue(
getFilter(UsernamePasswordAuthenticationFilter.class),"sessionStrategy.sessionRegistry");
Object sessionRegistryFromMgmtFilter = FieldUtils.getFieldValue(
getFilter(SessionManagementFilter.class),"sessionAuthenticationStrategy.sessionRegistry");
Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue(getFilter(UsernamePasswordAuthenticationFilter),"sessionStrategy").delegateStrategies[0].sessionRegistry
Object sessionRegistryFromMgmtFilter = FieldUtils.getFieldValue(getFilter(SessionManagementFilter),"sessionAuthenticationStrategy").delegateStrategies[0].sessionRegistry
assertSame(sessionRegistry, sessionRegistryFromConcurrencyFilter);
assertSame(sessionRegistry, sessionRegistryFromMgmtFilter);
@@ -297,7 +360,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
createAppContext()
expect:
!(getFilters("/someurl")[8] instanceof SessionManagementFilter)
!(getFilters("/someurl").find { it instanceof SessionManagementFilter})
}
def disablingSessionProtectionRetainsSessionManagementFilterInvalidSessionUrlSet() {