Add CsrfTokenRepository (#3805)

* Create LazyCsrfTokenRepository

Fixes gh-3790

* Add CookieCsrfTokenRepository

Fixes gh-3009
This commit is contained in:
Rob Winch
2016-04-12 16:26:53 -05:00
committed by Joe Grandja
parent e9cb92bb74
commit d3a9cc6eae
14 changed files with 1461 additions and 857 deletions

View File

@@ -15,11 +15,10 @@
*/
package org.springframework.security.config.annotation.web.configurers
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
import javax.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Configuration
import spock.lang.Unroll
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.config.annotation.BaseSpringSpec
@@ -27,15 +26,13 @@ import org.springframework.security.config.annotation.authentication.builders.Au
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.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.security.web.csrf.CsrfFilter
import org.springframework.security.web.csrf.CsrfTokenRepository
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.web.servlet.support.RequestDataValueProcessor
import spock.lang.Unroll
/**
*
* @author Rob Winch
@@ -45,31 +42,31 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Unroll
def "csrf applied by default"() {
setup:
loadConfig(CsrfAppliedDefaultConfig)
request.method = httpMethod
clearCsrfToken()
loadConfig(CsrfAppliedDefaultConfig)
request.method = httpMethod
clearCsrfToken()
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == httpStatus
response.status == httpStatus
where:
httpMethod | httpStatus
'POST' | HttpServletResponse.SC_FORBIDDEN
'PUT' | HttpServletResponse.SC_FORBIDDEN
'PATCH' | HttpServletResponse.SC_FORBIDDEN
'DELETE' | HttpServletResponse.SC_FORBIDDEN
'INVALID' | HttpServletResponse.SC_FORBIDDEN
'GET' | HttpServletResponse.SC_OK
'HEAD' | HttpServletResponse.SC_OK
'TRACE' | HttpServletResponse.SC_OK
'OPTIONS' | HttpServletResponse.SC_OK
httpMethod | httpStatus
'POST' | HttpServletResponse.SC_FORBIDDEN
'PUT' | HttpServletResponse.SC_FORBIDDEN
'PATCH' | HttpServletResponse.SC_FORBIDDEN
'DELETE' | HttpServletResponse.SC_FORBIDDEN
'INVALID' | HttpServletResponse.SC_FORBIDDEN
'GET' | HttpServletResponse.SC_OK
'HEAD' | HttpServletResponse.SC_OK
'TRACE' | HttpServletResponse.SC_OK
'OPTIONS' | HttpServletResponse.SC_OK
}
def "csrf default creates CsrfRequestDataValueProcessor"() {
when:
loadConfig(CsrfAppliedDefaultConfig)
loadConfig(CsrfAppliedDefaultConfig)
then:
context.getBean(RequestDataValueProcessor)
context.getBean(RequestDataValueProcessor)
}
@EnableWebSecurity
@@ -82,14 +79,14 @@ class CsrfConfigurerTests extends BaseSpringSpec {
def "csrf disable"() {
setup:
loadConfig(DisableCsrfConfig)
request.method = "POST"
clearCsrfToken()
loadConfig(DisableCsrfConfig)
request.method = "POST"
clearCsrfToken()
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
!findFilter(CsrfFilter)
response.status == HttpServletResponse.SC_OK
!findFilter(CsrfFilter)
response.status == HttpServletResponse.SC_OK
}
@EnableWebSecurity
@@ -98,29 +95,29 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.csrf().disable()
}
}
def "SEC-2498: Disable CSRF enables RequestCache for any method"() {
setup:
loadConfig(DisableCsrfEnablesRequestCacheConfig)
request.requestURI = '/tosave'
request.method = "POST"
clearCsrfToken()
loadConfig(DisableCsrfEnablesRequestCacheConfig)
request.requestURI = '/tosave'
request.method = "POST"
clearCsrfToken()
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.redirectedUrl
response.redirectedUrl
when:
super.setupWeb(request.session)
request.method = "POST"
request.servletPath = '/login'
request.parameters['username'] = ['user'] as String[]
request.parameters['password'] = ['password'] as String[]
springSecurityFilterChain.doFilter(request,response,chain)
super.setupWeb(request.session)
request.method = "POST"
request.servletPath = '/login'
request.parameters['username'] = ['user'] as String[]
request.parameters['password'] = ['password'] as String[]
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.redirectedUrl == 'http://localhost/tosave'
response.redirectedUrl == 'http://localhost/tosave'
}
@EnableWebSecurity
@@ -129,38 +126,37 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.csrf().disable()
.formLogin().and()
.csrf().disable()
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
def "SEC-2422: csrf expire CSRF token and session-management invalid-session-url"() {
setup:
loadConfig(InvalidSessionUrlConfig)
request.session.clearAttributes()
request.setParameter("_csrf","abc")
request.method = "POST"
loadConfig(InvalidSessionUrlConfig)
request.session.clearAttributes()
request.setParameter("_csrf","abc")
request.method = "POST"
when: "No existing expected CsrfToken (session times out) and a POST"
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the session timeout page page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/error/sessionError"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/error/sessionError"
when: "Existing expected CsrfToken and a POST (invalid token provided)"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session, method:'POST')
springSecurityFilterChain.doFilter(request,response,chain)
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session, method:'POST')
springSecurityFilterChain.doFilter(request,response,chain)
then: "Access Denied occurs"
response.status == HttpServletResponse.SC_FORBIDDEN
response.status == HttpServletResponse.SC_FORBIDDEN
}
@EnableWebSecurity
@@ -168,26 +164,26 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().and()
.sessionManagement()
.csrf().and()
.sessionManagement()
.invalidSessionUrl("/error/sessionError")
}
}
def "csrf requireCsrfProtectionMatcher"() {
setup:
RequireCsrfProtectionMatcherConfig.matcher = Mock(RequestMatcher)
RequireCsrfProtectionMatcherConfig.matcher.matches(_) >>> [false,true]
loadConfig(RequireCsrfProtectionMatcherConfig)
clearCsrfToken()
RequireCsrfProtectionMatcherConfig.matcher = Mock(RequestMatcher)
RequireCsrfProtectionMatcherConfig.matcher.matches(_) >>> [false, true]
loadConfig(RequireCsrfProtectionMatcherConfig)
clearCsrfToken()
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
response.status == HttpServletResponse.SC_OK
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_FORBIDDEN
response.status == HttpServletResponse.SC_FORBIDDEN
}
@EnableWebSecurity
@@ -197,53 +193,53 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrf()
.requireCsrfProtectionMatcher(matcher)
}
}
def "csrf csrfTokenRepository"() {
setup:
CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
loadConfig(CsrfTokenRepositoryConfig)
clearCsrfToken()
CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
loadConfig(CsrfTokenRepositoryConfig)
clearCsrfToken()
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
1 * CsrfTokenRepositoryConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_OK
1 * CsrfTokenRepositoryConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_OK
}
def "csrf clears on logout"() {
setup:
CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
1 * CsrfTokenRepositoryConfig.repo.loadToken(_) >> csrfToken
loadConfig(CsrfTokenRepositoryConfig)
login()
request.method = "POST"
request.servletPath = "/logout"
CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
1 * CsrfTokenRepositoryConfig.repo.loadToken(_) >> csrfToken
loadConfig(CsrfTokenRepositoryConfig)
login()
request.method = "POST"
request.servletPath = "/logout"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
1 * CsrfTokenRepositoryConfig.repo.saveToken(null, _, _)
1 * CsrfTokenRepositoryConfig.repo.saveToken(null, _, _)
}
def "csrf clears on login"() {
setup:
CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
(1.._) * CsrfTokenRepositoryConfig.repo.loadToken(_) >> csrfToken
(1.._) * CsrfTokenRepositoryConfig.repo.generateToken(_) >> csrfToken
loadConfig(CsrfTokenRepositoryConfig)
request.method = "POST"
request.getSession()
request.servletPath = "/login"
request.setParameter("username", "user")
request.setParameter("password", "password")
CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
(1.._) * CsrfTokenRepositoryConfig.repo.loadToken(_) >> csrfToken
(1.._) * CsrfTokenRepositoryConfig.repo.generateToken(_) >> csrfToken
loadConfig(CsrfTokenRepositoryConfig)
request.method = "POST"
request.getSession()
request.servletPath = "/login"
request.setParameter("username", "user")
request.setParameter("password", "password")
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.redirectedUrl == "/"
(1.._) * CsrfTokenRepositoryConfig.repo.saveToken(null, _, _)
response.redirectedUrl == "/"
(1.._) * CsrfTokenRepositoryConfig.repo.saveToken(null, _, _)
}
@EnableWebSecurity
@@ -253,30 +249,30 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.formLogin()
.and()
.csrf()
.csrf()
.csrfTokenRepository(repo)
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
def "csrf access denied handler"() {
setup:
AccessDeniedHandlerConfig.deniedHandler = Mock(AccessDeniedHandler)
1 * AccessDeniedHandlerConfig.deniedHandler.handle(_, _, _)
loadConfig(AccessDeniedHandlerConfig)
clearCsrfToken()
request.method = "POST"
AccessDeniedHandlerConfig.deniedHandler = Mock(AccessDeniedHandler)
1 * AccessDeniedHandlerConfig.deniedHandler.handle(_, _, _)
loadConfig(AccessDeniedHandlerConfig)
clearCsrfToken()
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
response.status == HttpServletResponse.SC_OK
}
@EnableWebSecurity
@@ -286,24 +282,24 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.exceptionHandling()
.accessDeniedHandler(deniedHandler)
}
}
def "formLogin requires CSRF token"() {
setup:
loadConfig(FormLoginConfig)
clearCsrfToken()
request.setParameter("username", "user")
request.setParameter("password", "password")
request.servletPath = "/login"
request.method = "POST"
loadConfig(FormLoginConfig)
clearCsrfToken()
request.setParameter("username", "user")
request.setParameter("password", "password")
request.servletPath = "/login"
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_FORBIDDEN
currentAuthentication == null
response.status == HttpServletResponse.SC_FORBIDDEN
currentAuthentication == null
}
@EnableWebSecurity
@@ -313,34 +309,34 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.formLogin()
}
}
def "logout requires CSRF token"() {
setup:
loadConfig(LogoutConfig)
clearCsrfToken()
login()
request.servletPath = "/logout"
request.method = "POST"
loadConfig(LogoutConfig)
clearCsrfToken()
login()
request.servletPath = "/logout"
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then: "logout is not allowed and user is still authenticated"
response.status == HttpServletResponse.SC_FORBIDDEN
currentAuthentication != null
response.status == HttpServletResponse.SC_FORBIDDEN
currentAuthentication != null
}
def "SEC-2543: CSRF means logout requires POST"() {
setup:
loadConfig(LogoutConfig)
login()
request.servletPath = "/logout"
request.method = "GET"
loadConfig(LogoutConfig)
login()
request.servletPath = "/logout"
request.method = "GET"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then: "logout with GET is not performed"
currentAuthentication != null
currentAuthentication != null
}
@EnableWebSecurity
@@ -350,20 +346,20 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.formLogin()
}
}
def "CSRF can explicitly enable GET for logout"() {
setup:
loadConfig(LogoutAllowsGetConfig)
login()
request.servletPath = "/logout"
request.method = "GET"
loadConfig(LogoutAllowsGetConfig)
login()
request.servletPath = "/logout"
request.method = "GET"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then: "logout with GET is not performed"
currentAuthentication == null
currentAuthentication == null
}
@EnableWebSecurity
@@ -373,64 +369,64 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().and()
.logout()
.formLogin().and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
}
}
def "csrf disables POST requests from RequestCache"() {
setup:
CsrfDisablesPostRequestFromRequestCacheConfig.repo = Mock(CsrfTokenRepository)
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.generateToken(_) >> csrfToken
loadConfig(CsrfDisablesPostRequestFromRequestCacheConfig)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "POST"
CsrfDisablesPostRequestFromRequestCacheConfig.repo = Mock(CsrfTokenRepository)
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.generateToken(_) >> csrfToken
loadConfig(CsrfDisablesPostRequestFromRequestCacheConfig)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "POST"
when: "CSRF passes and our session times out"
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default success because we don't want csrf attempts made prior to authentication to pass"
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
def "csrf enables GET requests with RequestCache"() {
setup:
CsrfDisablesPostRequestFromRequestCacheConfig.repo = Mock(CsrfTokenRepository)
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.generateToken(_) >> csrfToken
loadConfig(CsrfDisablesPostRequestFromRequestCacheConfig)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "GET"
CsrfDisablesPostRequestFromRequestCacheConfig.repo = Mock(CsrfTokenRepository)
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.generateToken(_) >> csrfToken
loadConfig(CsrfDisablesPostRequestFromRequestCacheConfig)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "GET"
when: "CSRF passes and our session times out"
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to original URL since it was a GET"
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/some-url"
(1.._) * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/some-url"
}
@EnableWebSecurity
@@ -440,18 +436,18 @@ class CsrfConfigurerTests extends BaseSpringSpec {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.formLogin()
.and()
.csrf()
.csrf()
.csrfTokenRepository(repo)
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
@@ -463,6 +459,39 @@ class CsrfConfigurerTests extends BaseSpringSpec {
thrown(IllegalArgumentException)
}
def 'default does not create session'() {
setup:
request = new MockHttpServletRequest(method:'GET')
loadConfig(DefaultDoesNotCreateSession)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
request.getSession(false) == null
}
@EnableWebSecurity(debug=true)
static class DefaultDoesNotCreateSession extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.formLogin().and()
.httpBasic();
// @formatter:on
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
def clearCsrfToken() {
request.removeAllParameters()
}

View File

@@ -12,12 +12,11 @@
*/
package org.springframework.security.config.http
import static org.mockito.Matchers.*
import static org.mockito.Mockito.*
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import spock.lang.Unroll
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
@@ -36,7 +35,8 @@ import org.springframework.security.web.csrf.DefaultCsrfToken
import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.web.servlet.support.RequestDataValueProcessor
import spock.lang.Unroll
import static org.mockito.Matchers.*
import static org.mockito.Mockito.*
/**
*
@@ -73,256 +73,253 @@ class CsrfConfigTests extends AbstractHttpConfigTests {
def 'csrf disabled'() {
when:
httpAutoConfig {
csrf(disabled:true)
}
createAppContext()
httpAutoConfig { csrf(disabled:true) }
createAppContext()
then:
!getFilter(CsrfFilter)
!getFilter(CsrfFilter)
}
@Unroll
def 'csrf defaults'() {
setup:
httpAutoConfig {
'csrf'()
}
createAppContext()
httpAutoConfig { 'csrf'() }
createAppContext()
when:
request.method = httpMethod
springSecurityFilterChain.doFilter(request,response,chain)
request.method = httpMethod
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == httpStatus
response.status == httpStatus
where:
httpMethod | httpStatus
'POST' | HttpServletResponse.SC_FORBIDDEN
'PUT' | HttpServletResponse.SC_FORBIDDEN
'PATCH' | HttpServletResponse.SC_FORBIDDEN
'DELETE' | HttpServletResponse.SC_FORBIDDEN
'INVALID' | HttpServletResponse.SC_FORBIDDEN
'GET' | HttpServletResponse.SC_OK
'HEAD' | HttpServletResponse.SC_OK
'TRACE' | HttpServletResponse.SC_OK
'OPTIONS' | HttpServletResponse.SC_OK
httpMethod | httpStatus
'POST' | HttpServletResponse.SC_FORBIDDEN
'PUT' | HttpServletResponse.SC_FORBIDDEN
'PATCH' | HttpServletResponse.SC_FORBIDDEN
'DELETE' | HttpServletResponse.SC_FORBIDDEN
'INVALID' | HttpServletResponse.SC_FORBIDDEN
'GET' | HttpServletResponse.SC_OK
'HEAD' | HttpServletResponse.SC_OK
'TRACE' | HttpServletResponse.SC_OK
'OPTIONS' | HttpServletResponse.SC_OK
}
def 'csrf default creates CsrfRequestDataValueProcessor'() {
when:
httpAutoConfig {
'csrf'()
}
createAppContext()
httpAutoConfig { 'csrf'() }
createAppContext()
then:
appContext.getBean("requestDataValueProcessor",RequestDataValueProcessor)
appContext.getBean("requestDataValueProcessor",RequestDataValueProcessor)
}
def 'csrf custom AccessDeniedHandler'() {
setup:
httpAutoConfig {
'access-denied-handler'(ref:'adh')
'csrf'()
}
mockBean(AccessDeniedHandler,'adh')
createAppContext()
AccessDeniedHandler adh = appContext.getBean(AccessDeniedHandler)
request.method = "POST"
httpAutoConfig {
'access-denied-handler'(ref:'adh')
'csrf'()
}
mockBean(AccessDeniedHandler,'adh')
createAppContext()
AccessDeniedHandler adh = appContext.getBean(AccessDeniedHandler)
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
verify(adh).handle(any(HttpServletRequest),any(HttpServletResponse),any(AccessDeniedException))
response.status == HttpServletResponse.SC_OK // our mock doesn't do anything
verify(adh).handle(any(HttpServletRequest),any(HttpServletResponse),any(AccessDeniedException))
response.status == HttpServletResponse.SC_OK // our mock doesn't do anything
}
def "csrf disables posts for RequestCache"() {
setup:
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
'intercept-url'(pattern:"/**",access:'ROLE_USER')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
when(repo.generateToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "POST"
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
'intercept-url'(pattern:"/**",access:'ROLE_USER')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
when(repo.generateToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "POST"
when: "CSRF passes and our session times out"
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session)
request.servletPath = "/login"
request.setParameter(token.parameterName,token.token)
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session)
request.servletPath = "/login"
request.setParameter(token.parameterName,token.token)
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default success because we don't want csrf attempts made prior to authentication to pass"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
def "csrf enables gets for RequestCache"() {
setup:
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
'intercept-url'(pattern:"/**",access:'ROLE_USER')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
'intercept-url'(pattern:"/**",access:'ROLE_USER')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
when(repo.generateToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "GET"
request.setParameter(token.parameterName,token.token)
request.servletPath = "/some-url"
request.requestURI = "/some-url"
request.method = "GET"
when: "CSRF passes and our session times out"
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session)
request.servletPath = "/login"
request.setParameter(token.parameterName,token.token)
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session)
request.servletPath = "/login"
request.setParameter(token.parameterName,token.token)
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to original URL since it was a GET"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/some-url"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/some-url"
}
def "SEC-2422: csrf expire CSRF token and session-management invalid-session-url"() {
setup:
httpAutoConfig {
'csrf'()
'session-management'('invalid-session-url': '/error/sessionError')
}
createAppContext()
request.setParameter("_csrf","abc")
request.method = "POST"
httpAutoConfig {
'csrf'()
'session-management'('invalid-session-url': '/error/sessionError')
}
createAppContext()
request.setParameter("_csrf","abc")
request.method = "POST"
when: "No existing expected CsrfToken (session times out) and a POST"
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the session timeout page page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/error/sessionError"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/error/sessionError"
when: "Existing expected CsrfToken and a POST (invalid token provided)"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session, method:'POST')
springSecurityFilterChain.doFilter(request,response,chain)
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session, method:'POST')
springSecurityFilterChain.doFilter(request,response,chain)
then: "Access Denied occurs"
response.status == HttpServletResponse.SC_FORBIDDEN
response.status == HttpServletResponse.SC_FORBIDDEN
}
def "csrf requireCsrfProtectionMatcher"() {
setup:
httpAutoConfig {
'csrf'('request-matcher-ref':'matcher')
}
mockBean(RequestMatcher,'matcher')
createAppContext()
request.method = 'POST'
RequestMatcher matcher = appContext.getBean("matcher",RequestMatcher)
httpAutoConfig { 'csrf'('request-matcher-ref':'matcher') }
mockBean(RequestMatcher,'matcher')
createAppContext()
request.method = 'POST'
RequestMatcher matcher = appContext.getBean("matcher",RequestMatcher)
when:
when(matcher.matches(any(HttpServletRequest))).thenReturn(false)
springSecurityFilterChain.doFilter(request,response,chain)
when(matcher.matches(any(HttpServletRequest))).thenReturn(false)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
response.status == HttpServletResponse.SC_OK
when:
when(matcher.matches(any(HttpServletRequest))).thenReturn(true)
springSecurityFilterChain.doFilter(request,response,chain)
when(matcher.matches(any(HttpServletRequest))).thenReturn(true)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_FORBIDDEN
response.status == HttpServletResponse.SC_FORBIDDEN
}
def "csrf csrfTokenRepository default delays save"() {
setup:
httpAutoConfig {
}
createAppContext()
request.method = "GET"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
request.getSession(false) == null
}
def "csrf csrfTokenRepository"() {
setup:
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
httpAutoConfig { 'csrf'('token-repository-ref':'repo') }
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
response.status == HttpServletResponse.SC_OK
when:
request.setParameter(token.parameterName,token.token+"INVALID")
springSecurityFilterChain.doFilter(request,response,chain)
request.setParameter(token.parameterName,token.token+"INVALID")
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_FORBIDDEN
response.status == HttpServletResponse.SC_FORBIDDEN
}
def "csrf clears on login"() {
setup:
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
when(repo.generateToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
request.setParameter("username","user")
request.setParameter("password","password")
request.servletPath = "/login"
httpAutoConfig { 'csrf'('token-repository-ref':'repo') }
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
when(repo.generateToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
request.setParameter("username","user")
request.setParameter("password","password")
request.servletPath = "/login"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
verify(repo, atLeastOnce()).saveToken(eq(null),any(HttpServletRequest), any(HttpServletResponse))
verify(repo, atLeastOnce()).saveToken(eq(null),any(HttpServletRequest), any(HttpServletResponse))
}
def "csrf clears on logout"() {
setup:
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
request.servletPath = "/logout"
httpAutoConfig { 'csrf'('token-repository-ref':'repo') }
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
request.servletPath = "/logout"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
verify(repo).saveToken(eq(null),any(HttpServletRequest), any(HttpServletResponse))
verify(repo).saveToken(eq(null),any(HttpServletRequest), any(HttpServletResponse))
}
def "SEC-2495: csrf disables logout on GET"() {
setup:
httpAutoConfig {
'csrf'()
}
createAppContext()
login()
request.method = "GET"
request.requestURI = "/logout"
httpAutoConfig { 'csrf'() }
createAppContext()
login()
request.method = "GET"
request.requestURI = "/logout"
when:
springSecurityFilterChain.doFilter(request,response,chain)
springSecurityFilterChain.doFilter(request,response,chain)
then:
getAuthentication(request) != null
getAuthentication(request) != null
}