SEC-1574: Add CSRF Support

This commit is contained in:
Rob Winch
2013-08-15 14:49:21 -05:00
parent 5f35d9e3ec
commit e9bb9e766e
93 changed files with 2895 additions and 348 deletions

View File

@@ -1,6 +1,8 @@
package org.springframework.security.config
import groovy.xml.MarkupBuilder
import org.mockito.Mockito;
import org.springframework.context.support.AbstractXmlApplicationContext
import org.springframework.security.config.util.InMemoryXmlApplicationContext
import org.springframework.security.core.context.SecurityContextHolder
@@ -37,6 +39,12 @@ abstract class AbstractXmlConfigTests extends Specification {
SecurityContextHolder.clearContext();
}
def mockBean(Class clazz, String id = clazz.simpleName) {
xml.'b:bean'(id: id, 'class': Mockito.class.name, 'factory-method':'mock') {
'b:constructor-arg'(value : clazz.name)
}
}
def bean(String name, Class clazz) {
xml.'b:bean'(id: name, 'class': clazz.name)
}

View File

@@ -25,16 +25,18 @@ import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.context.SecurityContextImpl
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.context.HttpRequestResponseHolder
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.security.web.csrf.CsrfToken
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository
import spock.lang.AutoCleanup
import spock.lang.Specification
@@ -50,11 +52,26 @@ abstract class BaseSpringSpec extends Specification {
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
CsrfToken csrfToken
def setup() {
setupWeb(null)
}
def setupWeb(httpSession = null) {
request = new MockHttpServletRequest(method:"GET")
if(httpSession) {
request.session = httpSession
}
response = new MockHttpServletResponse()
chain = new MockFilterChain()
setupCsrf()
}
def setupCsrf(csrfTokenValue="BaseSpringSpec_CSRFTOKEN") {
csrfToken = new CsrfToken("X-CSRF-TOKEN","_csrf",csrfTokenValue)
new HttpSessionCsrfTokenRepository().saveToken(csrfToken, request,response)
request.setParameter(csrfToken.parameterName, csrfToken.token)
}
AuthenticationManagerBuilder authenticationBldr = new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR).inMemoryAuthentication().and()
@@ -117,6 +134,10 @@ abstract class BaseSpringSpec extends Specification {
authenticationProviders().find { provider.isAssignableFrom(it.class) }
}
def getCurrentAuthentication() {
new HttpSessionSecurityContextRepository().loadContext(new HttpRequestResponseHolder(request, response)).authentication
}
def login(String username="user", String role="ROLE_USER") {
login(new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(role)))
}

View File

@@ -1,52 +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
*
* 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.annotation;
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.FilterChainProxy;
import spock.lang.AutoCleanup
import spock.lang.Specification
/**
*
* @author Rob Winch
*/
abstract class BaseWebSpecuritySpec extends BaseSpringSpec {
FilterChainProxy springSecurityFilterChain
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
def setup() {
request = new MockHttpServletRequest(method:"GET")
response = new MockHttpServletResponse()
chain = new MockFilterChain()
}
def loadConfig(Class<?>... configs) {
super.loadConfig(configs)
springSecurityFilterChain = context.getBean(FilterChainProxy)
}
}

View File

@@ -20,8 +20,7 @@ import javax.servlet.http.HttpServletResponse
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.annotation.BaseWebSpecuritySpec
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.builders.WebSecurity
@@ -34,7 +33,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
* @author Rob Winch
*
*/
public class SampleWebSecurityConfigurerAdapterTests extends BaseWebSpecuritySpec {
public class SampleWebSecurityConfigurerAdapterTests extends BaseSpringSpec {
def "README HelloWorld Sample works"() {
setup: "Sample Config is loaded"
loadConfig(HelloWorldWebSecurityConfigurerAdapter)

View File

@@ -79,7 +79,8 @@ class WebSecurityConfigurerAdapterTests extends BaseSpringSpec {
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
'Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
'Pragma':'no-cache',
'X-XSS-Protection' : '1; mode=block']
'X-XSS-Protection' : '1; mode=block',
'X-CSRF-TOKEN' : csrfToken.token]
}
@EnableWebSecurity

View File

@@ -0,0 +1,355 @@
/*
* 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
*
* 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.annotation.web.configurers
import javax.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Configuration
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.web.access.AccessDeniedHandler
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.security.web.util.RequestMatcher;
import spock.lang.Unroll;
/**
*
* @author Rob Winch
*/
class CsrfConfigurerTests extends BaseSpringSpec {
@Unroll
def "csrf applied by default"() {
setup:
loadConfig(CsrfAppliedDefaultConfig)
request.method = httpMethod
clearCsrfToken()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
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
}
def "csrf default creates CsrfRequestDataValueProcessor"() {
when:
loadConfig(CsrfAppliedDefaultConfig)
then:
context.getBean(CsrfRequestDataValueProcessor)
}
@Configuration
@EnableWebSecurity
static class CsrfAppliedDefaultConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
def "csrf disable"() {
setup:
loadConfig(DisableCsrfConfig)
request.method = "POST"
clearCsrfToken()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
!findFilter(CsrfFilter)
response.status == HttpServletResponse.SC_OK
}
@Configuration
@EnableWebSecurity
static class DisableCsrfConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
}
}
def "csrf requireCsrfProtectionMatcher"() {
setup:
RequireCsrfProtectionMatcherConfig.matcher = Mock(RequestMatcher)
RequireCsrfProtectionMatcherConfig.matcher.matches(_) >>> [false,true]
loadConfig(RequireCsrfProtectionMatcherConfig)
clearCsrfToken()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_FORBIDDEN
}
@Configuration
@EnableWebSecurity
static class RequireCsrfProtectionMatcherConfig extends WebSecurityConfigurerAdapter {
static RequestMatcher matcher
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.requireCsrfProtectionMatcher(matcher)
}
}
def "csrf csrfTokenRepository"() {
setup:
CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
loadConfig(CsrfTokenRepositoryConfig)
clearCsrfToken()
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
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"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
1 * CsrfTokenRepositoryConfig.repo.saveToken(null, _, _)
}
def "csrf clears on login"() {
setup:
CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
1 * CsrfTokenRepositoryConfig.repo.loadToken(_) >> 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)
then:
response.redirectedUrl == "/"
1 * CsrfTokenRepositoryConfig.repo.saveToken(null, _, _)
}
@Configuration
@EnableWebSecurity
static class CsrfTokenRepositoryConfig extends WebSecurityConfigurerAdapter {
static CsrfTokenRepository repo
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.csrf()
.csrfTokenRepository(repo)
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.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"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
}
@Configuration
@EnableWebSecurity
static class AccessDeniedHandlerConfig extends WebSecurityConfigurerAdapter {
static AccessDeniedHandler deniedHandler
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.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"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_FORBIDDEN
currentAuthentication == null
}
@Configuration
@EnableWebSecurity
static class FormLoginConfig extends WebSecurityConfigurerAdapter {
static AccessDeniedHandler deniedHandler
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
}
}
def "logout requires CSRF token"() {
setup:
loadConfig(LogoutConfig)
clearCsrfToken()
login()
request.servletPath = "/logout"
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then: "logout is not allowed and user is still authenticated"
response.status == HttpServletResponse.SC_FORBIDDEN
currentAuthentication != null
}
@Configuration
@EnableWebSecurity
static class LogoutConfig extends WebSecurityConfigurerAdapter {
static AccessDeniedHandler deniedHandler
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
}
}
def "csrf disables POST requests from RequestCache"() {
setup:
CsrfDisablesPostRequestFromRequestCacheConfig.repo = Mock(CsrfTokenRepository)
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)
then: "sent to the login page"
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)
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 == "/"
}
def "csrf enables GET requests with RequestCache"() {
setup:
CsrfDisablesPostRequestFromRequestCacheConfig.repo = Mock(CsrfTokenRepository)
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)
then: "sent to the login page"
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)
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"
}
@Configuration
@EnableWebSecurity
static class CsrfDisablesPostRequestFromRequestCacheConfig extends WebSecurityConfigurerAdapter {
static CsrfTokenRepository repo
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf()
.csrfTokenRepository(repo)
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
def clearCsrfToken() {
request.removeAllParameters()
}
}

View File

@@ -37,7 +37,8 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.authentication.logout.LogoutFilter
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
import org.springframework.security.web.csrf.CsrfFilter
import org.springframework.security.web.header.HeaderWriterFilter
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
@@ -107,17 +108,17 @@ class DefaultFiltersTests extends BaseSpringSpec {
def "FilterChainProxyBuilder ignoring resources"() {
when:
context = new AnnotationConfigApplicationContext(FilterChainProxyBuilderIgnoringConfig)
loadConfig(FilterChainProxyBuilderIgnoringConfig)
then:
List<DefaultSecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
filterChains.size() == 2
filterChains[0].requestMatcher.pattern == '/resources/**'
filterChains[0].filters.empty
filterChains[1].requestMatcher instanceof AnyRequestMatcher
filterChains[1].filters.collect { it.class } ==
[WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, LogoutFilter, RequestCacheAwareFilter,
SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter,
ExceptionTranslationFilter, FilterSecurityInterceptor ]
List<DefaultSecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
filterChains.size() == 2
filterChains[0].requestMatcher.pattern == '/resources/**'
filterChains[0].filters.empty
filterChains[1].requestMatcher instanceof AnyRequestMatcher
filterChains[1].filters.collect { it.class } ==
[WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter, RequestCacheAwareFilter,
SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter,
ExceptionTranslationFilter, FilterSecurityInterceptor ]
}
@Configuration
@@ -139,17 +140,16 @@ class DefaultFiltersTests extends BaseSpringSpec {
def "DefaultFilters.permitAll()"() {
when:
context = new AnnotationConfigApplicationContext(DefaultFiltersConfigPermitAll)
loadConfig(DefaultFiltersConfigPermitAll)
MockHttpServletResponse response = new MockHttpServletResponse()
request = new MockHttpServletRequest(servletPath : uri, queryString: query, method:"POST")
setupCsrf()
springSecurityFilterChain.doFilter(request, response, new MockFilterChain())
then:
FilterChainProxy filterChain = context.getBean(FilterChainProxy)
expect:
MockHttpServletResponse response = new MockHttpServletResponse()
filterChain.doFilter(new MockHttpServletRequest(servletPath : uri, queryString: query), response, new MockFilterChain())
response.redirectedUrl == null
response.redirectedUrl == "/login?logout"
where:
uri | query
"/logout" | null
uri | query
"/logout" | null
}
@Configuration

View File

@@ -42,28 +42,16 @@ import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFi
*
*/
public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
FilterChainProxy springSecurityFilterChain
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
def setup() {
request = new MockHttpServletRequest(method:"GET")
response = new MockHttpServletResponse()
chain = new MockFilterChain()
}
def "http/form-login default login generating page"() {
setup:
loadConfig(DefaultLoginPageConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
findFilter(DefaultLoginPageViewFilter)
response.getRedirectedUrl() == "http://localhost/login"
when: "request the login page"
setup()
super.setup()
request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain)
then:
@@ -73,10 +61,11 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
</table>
</form></body></html>"""
when: "fail to log in"
setup()
super.setup()
request.servletPath = "/login"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
@@ -84,7 +73,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
response.getRedirectedUrl() == "/login?error"
when: "request the error page"
HttpSession session = request.session
setup()
super.setup()
request.session = session
request.requestURI = "/login"
request.queryString = "error"
@@ -96,10 +85,11 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
</table>
</form></body></html>"""
when: "login success"
setup()
super.setup()
request.servletPath = "/login"
request.method = "POST"
request.parameters.username = ["user"] as String[]
@@ -112,7 +102,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
def "logout success renders"() {
setup:
loadConfig(DefaultLoginPageConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "logout success"
request.requestURI = "/login"
request.queryString = "logout"
@@ -125,6 +114,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
</table>
</form></body></html>"""
}
@@ -144,7 +134,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
def "custom logout success handler prevents rendering"() {
setup:
loadConfig(DefaultLoginPageCustomLogoutSuccessHandlerConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "logout success"
request.requestURI = "/login"
request.queryString = "logout"
@@ -172,7 +161,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
def "custom logout success url prevents rendering"() {
setup:
loadConfig(DefaultLoginPageCustomLogoutConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "logout success"
request.requestURI = "/login"
request.queryString = "logout"
@@ -200,9 +188,8 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
def "http/form-login default login with remember me"() {
setup:
loadConfig(DefaultLoginPageWithRememberMeConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "request the login page"
setup()
super.setup()
request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain)
then:
@@ -213,6 +200,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
</table>
</form></body></html>"""
}
@@ -234,7 +222,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
def "http/form-login default login with openid"() {
setup:
loadConfig(DefaultLoginPageWithOpenIDConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "request the login page"
request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain)
@@ -244,6 +231,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
</form></body></html>"""
}
@@ -262,7 +250,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
def "http/form-login default login with openid, form login, and rememberme"() {
setup:
loadConfig(DefaultLoginPageWithFormLoginOpenIDRememberMeConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "request the login page"
request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain)
@@ -274,6 +261,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
</table>
</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
<table>
@@ -281,6 +269,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<tr><td><input type='checkbox' name='remember-me'></td><td>Remember me on this computer.</td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
</form></body></html>"""
}

View File

@@ -41,6 +41,7 @@ import org.springframework.security.web.authentication.logout.LogoutFilter
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.header.HeaderWriterFilter
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
@@ -64,7 +65,7 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
filterChains[0].filters.empty
filterChains[1].requestMatcher instanceof AnyRequestMatcher
filterChains[1].filters.collect { it.class.name.contains('$') ? it.class.superclass : it.class } ==
[WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, LogoutFilter, UsernamePasswordAuthenticationFilter,
[WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter, UsernamePasswordAuthenticationFilter,
RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter,
AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor ]
@@ -78,7 +79,7 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
!authFilter.requiresAuthentication(new MockHttpServletRequest(servletPath : "/login", method: "GET"), new MockHttpServletResponse())
and: "SessionFixationProtectionStrategy is configured correctly"
SessionFixationProtectionStrategy sessionStrategy = ReflectionTestUtils.getField(authFilter,"sessionStrategy")
SessionFixationProtectionStrategy sessionStrategy = ReflectionTestUtils.getField(authFilter,"sessionStrategy").delegateStrategies.find { SessionFixationProtectionStrategy }
sessionStrategy.migrateSessionAttributes
and: "Exception handling is configured correctly"
@@ -112,11 +113,13 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
def "FormLogin.permitAll()"() {
when: "load formLogin() with permitAll"
context = new AnnotationConfigApplicationContext(FormLoginConfigPermitAll)
then: "the formLogin URLs are granted access"
FilterChainProxy filterChain = context.getBean(FilterChainProxy)
MockHttpServletResponse response = new MockHttpServletResponse()
filterChain.doFilter(new MockHttpServletRequest(servletPath : servletPath, requestURI: servletPath, queryString: query, method: method), response, new MockFilterChain())
request = new MockHttpServletRequest(servletPath : servletPath, requestURI: servletPath, queryString: query, method: method)
setupCsrf()
then: "the formLogin URLs are granted access"
filterChain.doFilter(request, response, new MockFilterChain())
response.redirectedUrl == redirectUrl
where:

View File

@@ -47,8 +47,11 @@ class LogoutConfigurerTests extends BaseSpringSpec {
def "invoke logout twice does not override"() {
when:
loadConfig(InvokeTwiceDoesNotOverride)
request.method = "POST"
request.servletPath = "/custom/logout"
findFilter(LogoutFilter).doFilter(request,response,chain)
then:
findFilter(LogoutFilter).filterProcessesUrl == "/custom/logout"
response.redirectedUrl == "/login?logout"
}
@Configuration

View File

@@ -39,35 +39,24 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
*
*/
public class NamespaceHttpBasicTests extends BaseSpringSpec {
FilterChainProxy springSecurityFilterChain
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
def setup() {
request = new MockHttpServletRequest()
response = new MockHttpServletResponse()
chain = new MockFilterChain()
}
def "http/http-basic"() {
setup:
loadConfig(HttpBasicConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_UNAUTHORIZED
when: "fail to log in"
setup()
login("user","invalid")
super.setup()
basicLogin("user","invalid")
springSecurityFilterChain.doFilter(request,response,chain)
then: "unauthorized"
response.status == HttpServletResponse.SC_UNAUTHORIZED
response.getHeader("WWW-Authenticate") == 'Basic realm="Spring Security Application"'
when: "login success"
setup()
login()
super.setup()
basicLogin()
then: "sent to default succes page"
!response.committed
}
@@ -86,9 +75,8 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec {
def "http@realm"() {
setup:
loadConfig(CustomHttpBasicConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
login("user","invalid")
basicLogin("user","invalid")
springSecurityFilterChain.doFilter(request,response,chain)
then: "unauthorized"
response.status == HttpServletResponse.SC_UNAUTHORIZED
@@ -109,7 +97,6 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec {
def "http-basic@authentication-details-source-ref"() {
when:
loadConfig(AuthenticationDetailsSourceHttpBasicConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
then:
findFilter(BasicAuthenticationFilter).authenticationDetailsSource.class == CustomAuthenticationDetailsSource
}
@@ -128,20 +115,20 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec {
def "http-basic@entry-point-ref"() {
setup:
loadConfig(EntryPointRefHttpBasicConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR
when: "fail to log in"
setup()
login("user","invalid")
super.setup()
basicLogin("user","invalid")
springSecurityFilterChain.doFilter(request,response,chain)
then: "custom"
response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR
when: "login success"
setup()
login()
super.setup()
basicLogin()
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default succes page"
!response.committed
}
@@ -162,7 +149,7 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec {
}
}
def login(String username="user",String password="password") {
def basicLogin(String username="user",String password="password") {
def credentials = username + ":" + password
request.addHeader("Authorization", "Basic " + credentials.bytes.encodeBase64())
}

View File

@@ -36,6 +36,7 @@ import org.springframework.security.web.util.AnyRequestMatcher
*
*/
public class NamespaceHttpHeadersTests extends BaseSpringSpec {
def "http/headers"() {
setup:
loadConfig(HeadersDefaultConfig)
@@ -48,7 +49,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
'Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
'Pragma':'no-cache',
'X-XSS-Protection' : '1; mode=block']
'X-XSS-Protection' : '1; mode=block',
'X-CSRF-TOKEN' : csrfToken.token]
}
@Configuration
@@ -68,7 +70,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
'Pragma':'no-cache']
'Pragma':'no-cache',
'X-CSRF-TOKEN' : csrfToken.token]
}
@Configuration
@@ -88,7 +91,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains']
responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
'X-CSRF-TOKEN' : csrfToken.token]
}
@Configuration
@@ -107,7 +111,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['Strict-Transport-Security': 'max-age=15768000']
responseHeaders == ['Strict-Transport-Security': 'max-age=15768000',
'X-CSRF-TOKEN' : csrfToken.token]
}
@Configuration
@@ -128,7 +133,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['X-Frame-Options': 'SAMEORIGIN']
responseHeaders == ['X-Frame-Options': 'SAMEORIGIN',
'X-CSRF-TOKEN' : csrfToken.token]
}
@Configuration
@@ -150,7 +156,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['X-Frame-Options': 'ALLOW-FROM https://example.com']
responseHeaders == ['X-Frame-Options': 'ALLOW-FROM https://example.com',
'X-CSRF-TOKEN' : csrfToken.token]
}
@@ -171,7 +178,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['X-XSS-Protection': '1; mode=block']
responseHeaders == ['X-XSS-Protection': '1; mode=block',
'X-CSRF-TOKEN' : csrfToken.token]
}
@Configuration
@@ -191,7 +199,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['X-XSS-Protection': '1']
responseHeaders == ['X-XSS-Protection': '1',
'X-CSRF-TOKEN' : csrfToken.token]
}
@Configuration
@@ -211,7 +220,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['X-Content-Type-Options': 'nosniff']
responseHeaders == ['X-Content-Type-Options': 'nosniff',
'X-CSRF-TOKEN' : csrfToken.token]
}
@Configuration
@@ -233,7 +243,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
responseHeaders == ['customHeaderName': 'customHeaderValue']
responseHeaders == ['customHeaderName': 'customHeaderValue',
'X-CSRF-TOKEN' : csrfToken.token]
}
@Configuration
@@ -245,4 +256,5 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
.addHeaderWriter(new StaticHeadersWriter("customHeaderName", "customHeaderValue"))
}
}
}

View File

@@ -71,23 +71,13 @@ import org.springframework.security.web.util.RequestMatcher
*
*/
public class NamespaceHttpLogoutTests extends BaseSpringSpec {
FilterChainProxy springSecurityFilterChain
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
def setup() {
request = new MockHttpServletRequest()
response = new MockHttpServletResponse()
chain = new MockFilterChain()
}
def "http/logout"() {
setup:
loadConfig(HttpLogoutConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
login()
request.setRequestURI("/logout")
request.servletPath = "/logout"
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
@@ -106,9 +96,9 @@ public class NamespaceHttpLogoutTests extends BaseSpringSpec {
def "http/logout custom"() {
setup:
loadConfig(CustomHttpLogoutConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
login()
request.setRequestURI("/custom-logout")
request.servletPath = "/custom-logout"
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
@@ -135,9 +125,9 @@ public class NamespaceHttpLogoutTests extends BaseSpringSpec {
def "http/logout@success-handler-ref"() {
setup:
loadConfig(SuccessHandlerRefHttpLogoutConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
login()
request.setRequestURI("/logout")
request.servletPath = "/logout"
request.method = "POST"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:

View File

@@ -44,21 +44,9 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
*
*/
public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
FilterChainProxy springSecurityFilterChain
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
def setup() {
request = new MockHttpServletRequest()
response = new MockHttpServletResponse()
chain = new MockFilterChain()
}
def "http/openid-login"() {
when:
loadConfig(OpenIDLoginConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
then:
findFilter(OpenIDAuthenticationFilter).consumer.class == OpenID4JavaConsumer
when:
@@ -66,7 +54,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
then:
response.getRedirectedUrl() == "http://localhost/login"
when: "fail to log in"
setup()
super.setup()
request.servletPath = "/login/openid"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
@@ -89,7 +77,6 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
def "http/openid-login/attribute-exchange"() {
when:
loadConfig(OpenIDLoginAttributeExchangeConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
OpenID4JavaConsumer consumer = findFilter(OpenIDAuthenticationFilter).consumer
then:
consumer.class == OpenID4JavaConsumer
@@ -117,7 +104,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
then:
response.getRedirectedUrl() == "http://localhost/login"
when: "fail to log in"
setup()
super.setup()
request.servletPath = "/login/openid"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
@@ -165,13 +152,12 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
def "http/openid-login custom"() {
setup:
loadConfig(OpenIDLoginCustomConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getRedirectedUrl() == "http://localhost/authentication/login"
when: "fail to log in"
setup()
super.setup()
request.servletPath = "/authentication/login/process"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
@@ -200,7 +186,6 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
when:
OpenIDLoginCustomRefsConfig.AUDS = Mock(AuthenticationUserDetailsService)
loadConfig(OpenIDLoginCustomRefsConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
then: "CustomWebAuthenticationDetailsSource is used"
findFilter(OpenIDAuthenticationFilter).authenticationDetailsSource.class == CustomWebAuthenticationDetailsSource
findAuthenticationProvider(OpenIDAuthenticationProvider).userDetailsService == OpenIDLoginCustomRefsConfig.AUDS

View File

@@ -55,22 +55,10 @@ import org.springframework.test.util.ReflectionTestUtils
*
*/
public class NamespaceHttpX509Tests extends BaseSpringSpec {
FilterChainProxy springSecurityFilterChain
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
def setup() {
request = new MockHttpServletRequest()
response = new MockHttpServletResponse()
chain = new MockFilterChain()
}
def "http/x509 can authenticate"() {
setup:
X509Certificate certificate = loadCert("rod.cer")
loadConfig(X509Config)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
springSecurityFilterChain.doFilter(request, response, chain);
@@ -148,7 +136,6 @@ public class NamespaceHttpX509Tests extends BaseSpringSpec {
setup:
X509Certificate certificate = loadCert("rodatexampledotcom.cer")
loadConfig(SubjectPrincipalRegexConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
springSecurityFilterChain.doFilter(request, response, chain);
@@ -182,7 +169,6 @@ public class NamespaceHttpX509Tests extends BaseSpringSpec {
setup:
X509Certificate certificate = loadCert("rodatexampledotcom.cer")
loadConfig(UserDetailsServiceRefConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
springSecurityFilterChain.doFilter(request, response, chain);
@@ -216,7 +202,6 @@ public class NamespaceHttpX509Tests extends BaseSpringSpec {
setup:
X509Certificate certificate = loadCert("rodatexampledotcom.cer")
loadConfig(AuthenticationUserDetailsServiceConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
springSecurityFilterChain.doFilter(request, response, chain);

View File

@@ -81,8 +81,10 @@ public class NamespaceRememberMeTests extends BaseSpringSpec {
when: "logout"
super.setup()
request.setSession(session)
super.setupCsrf()
request.setCookies(rememberMeCookie)
request.requestURI = "/logout"
request.servletPath = "/logout"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
rememberMeCookie = getRememberMeCookie()
then: "logout cookie expired"

View File

@@ -41,7 +41,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
when:
loadConfig(SessionManagementConfig)
then:
findFilter(SessionManagementFilter).sessionAuthenticationStrategy instanceof SessionFixationProtectionStrategy
findSessionAuthenticationStrategy(SessionFixationProtectionStrategy)
}
@EnableWebSecurity
@@ -91,7 +91,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
when:
loadConfig(RefsSessionManagementConfig)
then:
findFilter(SessionManagementFilter).sessionAuthenticationStrategy == RefsSessionManagementConfig.SAS
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it == RefsSessionManagementConfig.SAS }
}
@EnableWebSecurity
@@ -110,7 +110,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
when:
loadConfig(SFPNoneSessionManagementConfig)
then:
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.class == NullAuthenticatedSessionStrategy
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it instanceof NullAuthenticatedSessionStrategy }
}
@EnableWebSecurity
@@ -128,7 +128,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
when:
loadConfig(SFPMigrateSessionManagementConfig)
then:
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.migrateSessionAttributes
findSessionAuthenticationStrategy(SessionFixationProtectionStrategy).migrateSessionAttributes
}
@EnableWebSecurity
@@ -145,7 +145,11 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
when:
loadConfig(SFPNewSessionSessionManagementConfig)
then:
!findFilter(SessionManagementFilter).sessionAuthenticationStrategy.migrateSessionAttributes
!findSessionAuthenticationStrategy(SessionFixationProtectionStrategy).migrateSessionAttributes
}
def findSessionAuthenticationStrategy(def c) {
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it.class.isAssignableFrom(c) }
}
@EnableWebSecurity

View File

@@ -26,6 +26,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.csrf.CsrfLogoutHandler;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
/**
@@ -59,7 +60,7 @@ class ServletApiConfigurerTests extends BaseSpringSpec {
and: "requestFactory != null"
filter.requestFactory != null
and: "logoutHandlers populated"
filter.logoutHandlers.collect { it.class } == [SecurityContextLogoutHandler]
filter.logoutHandlers.collect { it.class } == [CsrfLogoutHandler, SecurityContextLogoutHandler]
}
@CompileStatic

View File

@@ -57,8 +57,11 @@ abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests {
}
List getFilters(String url) {
def fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
return fcp.getFilters(url)
springSecurityFilterChain.getFilters(url)
}
Filter getSpringSecurityFilterChain() {
appContext.getBean(BeanIds.FILTER_CHAIN_PROXY)
}
FilterInvocation createFilterinvocation(String path, String method) {

View File

@@ -0,0 +1,257 @@
/*
* Copyright 2002-2012 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 static org.mockito.Mockito.*
import static org.mockito.Matchers.*
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse
import org.spockframework.compiler.model.WhenBlock;
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurerTests.CsrfTokenRepositoryConfig;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurerTests.RequireCsrfProtectionMatcherConfig
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.csrf.CsrfFilter
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor
import org.springframework.security.web.util.RequestMatcher
import spock.lang.Unroll
/**
*
* @author Rob Winch
*/
class CsrfConfigTests extends AbstractHttpConfigTests {
MockHttpServletRequest request = new MockHttpServletRequest()
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
def 'no http csrf filter by default'() {
when:
httpAutoConfig {
}
createAppContext()
then:
!getFilter(CsrfFilter)
}
@Unroll
def 'csrf defaults'() {
setup:
httpAutoConfig {
'csrf'()
}
createAppContext()
when:
request.method = httpMethod
springSecurityFilterChain.doFilter(request,response,chain)
then:
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
}
def 'csrf default creates CsrfRequestDataValueProcessor'() {
when:
httpAutoConfig {
'csrf'()
}
createAppContext()
then:
appContext.getBean("requestDataValueProcessor",CsrfRequestDataValueProcessor)
}
def 'csrf custom AccessDeniedHandler'() {
setup:
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)
then:
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 CsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(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)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/spring_security_login"
when: "authenticate successfully"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session)
request.requestURI = "/j_spring_security_check"
request.setParameter(token.parameterName,token.token)
request.setParameter("j_username","user")
request.setParameter("j_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 == "/"
}
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 CsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
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)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/spring_security_login"
when: "authenticate successfully"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session)
request.requestURI = "/j_spring_security_check"
request.setParameter(token.parameterName,token.token)
request.setParameter("j_username","user")
request.setParameter("j_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"
}
def "csrf requireCsrfProtectionMatcher"() {
setup:
httpAutoConfig {
'csrf'('request-matcher-ref':'matcher')
}
mockBean(RequestMatcher,'matcher')
createAppContext()
RequestMatcher matcher = appContext.getBean("matcher",RequestMatcher)
when:
when(matcher.matches(any(HttpServletRequest))).thenReturn(false)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_OK
when:
when(matcher.matches(any(HttpServletRequest))).thenReturn(true)
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == HttpServletResponse.SC_FORBIDDEN
}
def "csrf csrfTokenRepository"() {
setup:
httpAutoConfig {
'csrf'('token-repository-ref':'repo')
}
mockBean(CsrfTokenRepository,'repo')
createAppContext()
CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository)
CsrfToken token = new CsrfToken("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)
then:
response.status == HttpServletResponse.SC_OK
when:
request.setParameter(token.parameterName,token.token+"INVALID")
springSecurityFilterChain.doFilter(request,response,chain)
then:
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 CsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
request.setParameter("j_username","user")
request.setParameter("j_password","password")
request.requestURI = "/j_spring_security_check"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
verify(repo).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 CsrfToken("X-CSRF-TOKEN","_csrf", "abc")
when(repo.loadToken(any(HttpServletRequest))).thenReturn(token)
request.setParameter(token.parameterName,token.token)
request.method = "POST"
request.requestURI = "/j_spring_security_logout"
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
verify(repo).saveToken(eq(null),any(HttpServletRequest), any(HttpServletResponse))
}
}

View File

@@ -275,9 +275,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
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)
}
mockBean(SessionAuthenticationStrategy,'ss')
createAppContext()
MockHttpServletRequest request = new MockHttpServletRequest();

View File

@@ -0,0 +1,85 @@
/*
* 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
*
* 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.annotation.web.configurers;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.when;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
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.util.ClassUtils;
/**
* @author Rob Winch
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassUtils.class})
public class CsrfConfigurerNoWebMvcTests {
ConfigurableApplicationContext context;
@After
public void teardown() {
if(context != null) {
context.close();
}
}
@Test
public void missingDispatcherServletPreventsCsrfRequestDataValueProcessor() {
spy(ClassUtils.class);
when(ClassUtils.isPresent(eq("org.springframework.web.servlet.DispatcherServlet"), any(ClassLoader.class))).thenReturn(false);
loadContext(CsrfDefaultsConfig.class);
assertThat(context.containsBeanDefinition("requestDataValueProcessor")).isFalse();
}
@Test
public void findDispatcherServletPreventsCsrfRequestDataValueProcessor() {
loadContext(CsrfDefaultsConfig.class);
assertThat(context.containsBeanDefinition("requestDataValueProcessor")).isTrue();
}
@EnableWebSecurity
@Configuration
static class CsrfDefaultsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
private void loadContext(Class<?> configs) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.register(configs);
annotationConfigApplicationContext.refresh();
this.context = annotationConfigApplicationContext;
}
}

View File

@@ -49,6 +49,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.util.ReflectionUtils;
@@ -94,6 +96,8 @@ public class SessionManagementConfigurerServlet31Tests {
request.setMethod("POST");
request.setParameter("username", "user");
request.setParameter("password", "password");
CsrfToken token = new HttpSessionCsrfTokenRepository().generateAndSaveToken(request, response);
request.setParameter(token.getParameterName(),token.getToken());
when(ReflectionUtils.findMethod(HttpServletRequest.class, "changeSessionId")).thenReturn(method);
loadConfig(SessionManagementDefaultSessionFixationServlet31Config.class);