diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java index e4cf68dd49..b0a146c10d 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java @@ -33,6 +33,7 @@ import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.security.web.csrf.CsrfLogoutHandler; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; +import org.springframework.security.web.csrf.LazyCsrfTokenRepository; import org.springframework.security.web.csrf.MissingCsrfTokenException; import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler; import org.springframework.security.web.session.InvalidSessionStrategy; @@ -43,8 +44,9 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; /** - * Adds CSRF protection for the methods as specified by + * Adds + * CSRF + * protection for the methods as specified by * {@link #requireCsrfProtectionMatcher(RequestMatcher)}. * *
- * The support works by associating the user to the HttpServletRequest. To - * associate the request to the SecurityContextHolder you need to ensure - * that the SecurityContextPersistenceFilter is associated with the - * MockMvc instance. A few ways to do this are: + * The support works by associating the user to the HttpServletRequest. To associate + * the request to the SecurityContextHolder you need to ensure that the + * SecurityContextPersistenceFilter is associated with the MockMvc instance. A few + * ways to do this are: *
* *- * The support works by associating the user to the HttpServletRequest. To - * associate the request to the SecurityContextHolder you need to ensure - * that the SecurityContextPersistenceFilter is associated with the - * MockMvc instance. A few ways to do this are: + * The support works by associating the user to the HttpServletRequest. To associate + * the request to the SecurityContextHolder you need to ensure that the + * SecurityContextPersistenceFilter is associated with the MockMvc instance. A few + * ways to do this are: *
* *- * The support works by associating the user to the HttpServletRequest. To - * associate the request to the SecurityContextHolder you need to ensure - * that the SecurityContextPersistenceFilter is associated with the - * MockMvc instance. A few ways to do this are: + * The support works by associating the user to the HttpServletRequest. To associate + * the request to the SecurityContextHolder you need to ensure that the + * SecurityContextPersistenceFilter is associated with the MockMvc instance. A few + * ways to do this are: *
* *
*
@@ -241,8 +244,7 @@ public final class SecurityMockMvcRequestPostProcessors {
* }
* // ... lots of tests ran with a default user ...
* }
- *
- *
+ *
*
* @return the {@link RequestPostProcessor} to use
*/
@@ -254,11 +256,10 @@ public final class SecurityMockMvcRequestPostProcessors {
* Establish the specified {@link SecurityContext} to be used.
*
* - * This works by associating the user to the {@link HttpServletRequest}. To - * associate the request to the {@link SecurityContextHolder} you need to - * ensure that the {@link SecurityContextPersistenceFilter} (i.e. Spring - * Security's FilterChainProxy will typically do this) is associated with - * the {@link MockMvc} instance. + * This works by associating the user to the {@link HttpServletRequest}. To associate + * the request to the {@link SecurityContextHolder} you need to ensure that the + * {@link SecurityContextPersistenceFilter} (i.e. Spring Security's FilterChainProxy + * will typically do this) is associated with the {@link MockMvc} instance. *
*/ public static RequestPostProcessor securityContext(SecurityContext securityContext) { @@ -289,8 +290,10 @@ public final class SecurityMockMvcRequestPostProcessors { this.certificates = certificates; } + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - request.setAttribute("javax.servlet.request.X509Certificate", certificates); + request.setAttribute("javax.servlet.request.X509Certificate", + this.certificates); return request; } } @@ -313,18 +316,20 @@ public final class SecurityMockMvcRequestPostProcessors { * @see org.springframework.test.web.servlet.request.RequestPostProcessor * #postProcessRequest (org.springframework.mock.web.MockHttpServletRequest) */ + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { CsrfTokenRepository repository = WebTestUtils.getCsrfTokenRepository(request); - if(!(repository instanceof TestCsrfTokenRepository)) { - repository = new TestCsrfTokenRepository(repository); + if (!(repository instanceof TestCsrfTokenRepository)) { + repository = new TestCsrfTokenRepository( + new HttpSessionCsrfTokenRepository()); WebTestUtils.setCsrfTokenRepository(request, repository); } CsrfToken token = repository.generateToken(request); repository.saveToken(token, request, new MockHttpServletResponse()); - String tokenValue = useInvalidToken ? "invalid" + token.getToken() : token - .getToken(); - if (asHeader) { + String tokenValue = this.useInvalidToken ? "invalid" + token.getToken() + : token.getToken(); + if (this.asHeader) { request.addHeader(token.getHeaderName(), tokenValue); } else { @@ -357,16 +362,13 @@ public final class SecurityMockMvcRequestPostProcessors { private CsrfRequestPostProcessor() { } - - /** - * Used to wrap the CsrfTokenRepository to provide support for testing - * when the request is wrapped (i.e. Spring Session is in use). + * Used to wrap the CsrfTokenRepository to provide support for testing when the + * request is wrapped (i.e. Spring Session is in use). */ - static class TestCsrfTokenRepository implements - CsrfTokenRepository { - final static String ATTR_NAME = TestCsrfTokenRepository.class - .getName().concat(".TOKEN"); + static class TestCsrfTokenRepository implements CsrfTokenRepository { + final static String ATTR_NAME = TestCsrfTokenRepository.class.getName() + .concat(".TOKEN"); private final CsrfTokenRepository delegate; @@ -374,14 +376,18 @@ public final class SecurityMockMvcRequestPostProcessors { this.delegate = delegate; } + @Override public CsrfToken generateToken(HttpServletRequest request) { - return delegate.generateToken(request); + return this.delegate.generateToken(request); } - public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { + @Override + public void saveToken(CsrfToken token, HttpServletRequest request, + HttpServletResponse response) { request.setAttribute(ATTR_NAME, token); } + @Override public CsrfToken loadToken(HttpServletRequest request) { return (CsrfToken) request.getAttribute(ATTR_NAME); } @@ -447,14 +453,16 @@ public final class SecurityMockMvcRequestPostProcessors { private String createAuthorizationHeader(MockHttpServletRequest request) { String uri = request.getRequestURI(); - String responseDigest = generateDigest(username, realm, password, - request.getMethod(), uri, qop, nonce, nc, cnonce); - return "Digest username=\"" + username + "\", realm=\"" + realm - + "\", nonce=\"" + nonce + "\", uri=\"" + uri + "\", response=\"" - + responseDigest + "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" - + cnonce + "\""; + String responseDigest = generateDigest(this.username, this.realm, + this.password, request.getMethod(), uri, this.qop, this.nonce, + this.nc, this.cnonce); + return "Digest username=\"" + this.username + "\", realm=\"" + this.realm + + "\", nonce=\"" + this.nonce + "\", uri=\"" + uri + "\", response=\"" + + responseDigest + "\", qop=" + this.qop + ", nc=" + this.nc + + ", cnonce=\"" + this.cnonce + "\""; } + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { request.addHeader("Authorization", createAuthorizationHeader(request)); @@ -574,8 +582,7 @@ public final class SecurityMockMvcRequestPostProcessors { * Used to wrap the SecurityContextRepository to provide support for testing in * stateless mode */ - static class TestSecurityContextRepository implements - SecurityContextRepository { + static class TestSecurityContextRepository implements SecurityContextRepository { private final static String ATTR_NAME = TestSecurityContextRepository.class .getName().concat(".REPO"); @@ -585,6 +592,7 @@ public final class SecurityMockMvcRequestPostProcessors { this.delegate = delegate; } + @Override public SecurityContext loadContext( HttpRequestResponseHolder requestResponseHolder) { SecurityContext result = getContext(requestResponseHolder.getRequest()); @@ -592,19 +600,22 @@ public final class SecurityMockMvcRequestPostProcessors { // holder are updated // remember the SecurityContextRepository is used in many different // locations - SecurityContext delegateResult = delegate + SecurityContext delegateResult = this.delegate .loadContext(requestResponseHolder); return result == null ? delegateResult : result; } + @Override public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { request.setAttribute(ATTR_NAME, context); - delegate.saveContext(context, request, response); + this.delegate.saveContext(context, request, response); } + @Override public boolean containsContext(HttpServletRequest request) { - return getContext(request) != null || delegate.containsContext(request); + return getContext(request) != null + || this.delegate.containsContext(request); } private static SecurityContext getContext(HttpServletRequest request) { @@ -625,15 +636,17 @@ public final class SecurityMockMvcRequestPostProcessors { SecurityContextRequestPostProcessorSupport implements RequestPostProcessor { private SecurityContext EMPTY = SecurityContextHolder.createEmptyContext(); + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { // TestSecurityContextHolder is only a default value - SecurityContext existingContext = TestSecurityContextRepository.getContext(request); - if(existingContext != null) { + SecurityContext existingContext = TestSecurityContextRepository + .getContext(request); + if (existingContext != null) { return request; } SecurityContext context = TestSecurityContextHolder.getContext(); - if(!EMPTY.equals(context)) { + if (!this.EMPTY.equals(context)) { save(context, request); } @@ -657,6 +670,7 @@ public final class SecurityMockMvcRequestPostProcessors { this.securityContext = securityContext; } + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { save(this.securityContext, request); return request; @@ -679,10 +693,11 @@ public final class SecurityMockMvcRequestPostProcessors { this.authentication = authentication; } + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { SecurityContext context = SecurityContextHolder.createEmptyContext(); - context.setAuthentication(authentication); - save(authentication, request); + context.setAuthentication(this.authentication); + save(this.authentication, request); return request; } } @@ -695,19 +710,20 @@ public final class SecurityMockMvcRequestPostProcessors { * @author Rob Winch * @since 4.0 */ - private final static class UserDetailsRequestPostProcessor implements - RequestPostProcessor { + private final static class UserDetailsRequestPostProcessor + implements RequestPostProcessor { private final RequestPostProcessor delegate; public UserDetailsRequestPostProcessor(UserDetails user) { Authentication token = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); - delegate = new AuthenticationRequestPostProcessor(token); + this.delegate = new AuthenticationRequestPostProcessor(token); } + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - return delegate.postProcessRequest(request); + return this.delegate.postProcessRequest(request); } } @@ -752,8 +768,8 @@ public final class SecurityMockMvcRequestPostProcessors { * {@link #authorities(GrantedAuthority...)}, but just not as flexible. * * @param roles The roles to populate. Note that if the role does not start with - * {@link #ROLE_PREFIX} it will automatically be prepended. This means by - * default {@code roles("ROLE_USER")} and {@code roles("USER")} are equivalent. + * {@link #ROLE_PREFIX} it will automatically be prepended. This means by default + * {@code roles("ROLE_USER")} and {@code roles("USER")} are equivalent. * @see #authorities(GrantedAuthority...) * @see #ROLE_PREFIX * @return the UserRequestPostProcessor for further customizations @@ -764,8 +780,7 @@ public final class SecurityMockMvcRequestPostProcessors { for (String role : roles) { if (role.startsWith(ROLE_PREFIX)) { throw new IllegalArgumentException( - "Role should not start with " - + ROLE_PREFIX + "Role should not start with " + ROLE_PREFIX + " since this method automatically prefixes with this value. Got " + role); } @@ -812,6 +827,7 @@ public final class SecurityMockMvcRequestPostProcessors { return this; } + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { UserDetailsRequestPostProcessor delegate = new UserDetailsRequestPostProcessor( createUser()); @@ -823,19 +839,27 @@ public final class SecurityMockMvcRequestPostProcessors { * @return the {@link User} for the principal */ private User createUser() { - return new User(username, password, enabled, accountNonExpired, - credentialsNonExpired, accountNonLocked, authorities); + return new User(this.username, this.password, this.enabled, + this.accountNonExpired, this.credentialsNonExpired, + this.accountNonLocked, this.authorities); } } - private static class AnonymousRequestPostProcessor extends SecurityContextRequestPostProcessorSupport implements RequestPostProcessor { - private AuthenticationRequestPostProcessor delegate = new AuthenticationRequestPostProcessor(new AnonymousAuthenticationToken("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))); + private static class AnonymousRequestPostProcessor extends + SecurityContextRequestPostProcessorSupport implements RequestPostProcessor { + private AuthenticationRequestPostProcessor delegate = new AuthenticationRequestPostProcessor( + new AnonymousAuthenticationToken("key", "anonymous", + AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))); - /* (non-Javadoc) - * @see org.springframework.test.web.servlet.request.RequestPostProcessor#postProcessRequest(org.springframework.mock.web.MockHttpServletRequest) + /* + * (non-Javadoc) + * + * @see org.springframework.test.web.servlet.request.RequestPostProcessor# + * postProcessRequest(org.springframework.mock.web.MockHttpServletRequest) */ + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - return delegate.postProcessRequest(request); + return this.delegate.postProcessRequest(request); } } @@ -853,8 +877,9 @@ public final class SecurityMockMvcRequestPostProcessors { this.headerValue = "Basic " + new String(Base64.encode(toEncode)); } + @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - request.addHeader("Authorization", headerValue); + request.addHeader("Authorization", this.headerValue); return request; } } diff --git a/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java new file mode 100644 index 0000000000..1a85dd9761 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java @@ -0,0 +1,126 @@ +/* + * Copyright 2012-2016 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.web.csrf; + +import java.util.UUID; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.util.WebUtils; + +/** + * A {@link CsrfTokenRepository} that persist the CSRF token in a cookie named + * "XSRF-TOKEN" and reads from the header "X-XSRF-TOKEN" following the conventions of + * AngularJS. + * + * @author Rob Winch + * @since 4.1 + */ +public final class CookieCsrfTokenRepository implements CsrfTokenRepository { + static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN"; + + static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"; + + static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN"; + + private String parameterName = DEFAULT_CSRF_PARAMETER_NAME; + + private String headerName = DEFAULT_CSRF_HEADER_NAME; + + private String cookieName = DEFAULT_CSRF_COOKIE_NAME; + + @Override + public CsrfToken generateToken(HttpServletRequest request) { + return new DefaultCsrfToken(this.headerName, this.parameterName, + createNewToken()); + } + + @Override + public void saveToken(CsrfToken token, HttpServletRequest request, + HttpServletResponse response) { + String tokenValue = token == null ? "" : token.getToken(); + Cookie cookie = new Cookie(this.cookieName, tokenValue); + cookie.setSecure(request.isSecure()); + cookie.setPath(getCookiePath(request)); + if (token == null) { + cookie.setMaxAge(0); + } + else { + cookie.setMaxAge(-1); + } + response.addCookie(cookie); + } + + @Override + public CsrfToken loadToken(HttpServletRequest request) { + Cookie cookie = WebUtils.getCookie(request, this.cookieName); + if (cookie == null) { + return null; + } + String token = cookie.getValue(); + if (!StringUtils.hasLength(token)) { + return null; + } + return new DefaultCsrfToken(this.headerName, this.parameterName, token); + } + + /** + * Sets the name of the HTTP request parameter that should be used to provide a token. + * + * @param parameterName the name of the HTTP request parameter that should be used to + * provide a token + */ + public void setParameterName(String parameterName) { + Assert.notNull(parameterName, "parameterName is not null"); + this.parameterName = parameterName; + } + + /** + * Sets the name of the HTTP header that should be used to provide the token + * + * @param headerName the name of the HTTP header that should be used to provide the + * token + */ + public void setHeaderName(String headerName) { + Assert.notNull(headerName, "headerName is not null"); + this.headerName = headerName; + } + + /** + * Sets the name of the cookie that the expected CSRF token is saved to and read from + * + * @param cookieName the name of the cookie that the expected CSRF token is saved to + * and read from + */ + public void setCookieName(String cookieName) { + Assert.notNull(cookieName, "cookieName is not null"); + this.cookieName = cookieName; + } + + private String getCookiePath(HttpServletRequest request) { + String contextPath = request.getContextPath(); + return contextPath.length() > 0 ? contextPath : "/"; + } + + private String createNewToken() { + return UUID.randomUUID().toString(); + } +} \ No newline at end of file diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java index 7a9fc862db..fdf1e2c8c1 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java @@ -52,6 +52,7 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt * #onAuthentication(org.springframework.security.core.Authentication, * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ + @Override public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException { @@ -60,96 +61,10 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt this.csrfTokenRepository.saveToken(null, request, response); CsrfToken newToken = this.csrfTokenRepository.generateToken(request); - CsrfToken tokenForRequest = new SaveOnAccessCsrfToken( - this.csrfTokenRepository, request, response, newToken); + this.csrfTokenRepository.saveToken(newToken, request, response); - request.setAttribute(CsrfToken.class.getName(), tokenForRequest); - request.setAttribute(newToken.getParameterName(), tokenForRequest); + request.setAttribute(CsrfToken.class.getName(), newToken); + request.setAttribute(newToken.getParameterName(), newToken); } } - - private static final class SaveOnAccessCsrfToken implements CsrfToken { - private transient CsrfTokenRepository tokenRepository; - private transient HttpServletRequest request; - private transient HttpServletResponse response; - - private final CsrfToken delegate; - - public SaveOnAccessCsrfToken(CsrfTokenRepository tokenRepository, - HttpServletRequest request, HttpServletResponse response, - CsrfToken delegate) { - super(); - this.tokenRepository = tokenRepository; - this.request = request; - this.response = response; - this.delegate = delegate; - } - - public String getHeaderName() { - return this.delegate.getHeaderName(); - } - - public String getParameterName() { - return this.delegate.getParameterName(); - } - - public String getToken() { - saveTokenIfNecessary(); - return this.delegate.getToken(); - } - - @Override - public String toString() { - return "SaveOnAccessCsrfToken [delegate=" + this.delegate + "]"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + ((this.delegate == null) ? 0 : this.delegate.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - SaveOnAccessCsrfToken other = (SaveOnAccessCsrfToken) obj; - if (this.delegate == null) { - if (other.delegate != null) { - return false; - } - } - else if (!this.delegate.equals(other.delegate)) { - return false; - } - return true; - } - - private void saveTokenIfNecessary() { - if (this.tokenRepository == null) { - return; - } - - synchronized (this) { - if (this.tokenRepository != null) { - this.tokenRepository.saveToken(this.delegate, this.request, - this.response); - this.tokenRepository = null; - this.request = null; - this.response = null; - } - } - } - - } } diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java index 03c155178e..0a62e303bc 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java @@ -27,27 +27,29 @@ import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; -import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.UrlUtils; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; /** *- * Applies CSRF protection using a synchronizer token pattern. Developers are required to - * ensure that {@link CsrfFilter} is invoked for any request that allows state to change. - * Typically this just means that they should ensure their web application follows proper - * REST semantics (i.e. do not change state with the HTTP methods GET, HEAD, TRACE, - * OPTIONS). + * Applies + * CSRF + * protection using a synchronizer token pattern. Developers are required to ensure that + * {@link CsrfFilter} is invoked for any request that allows state to change. Typically + * this just means that they should ensure their web application follows proper REST + * semantics (i.e. do not change state with the HTTP methods GET, HEAD, TRACE, OPTIONS). *
* ** Typically the {@link CsrfTokenRepository} implementation chooses to store the - * {@link CsrfToken} in {@link HttpSession} with {@link HttpSessionCsrfTokenRepository}. - * This is preferred to storing the token in a cookie which can be modified by a client application. + * {@link CsrfToken} in {@link HttpSession} with {@link HttpSessionCsrfTokenRepository} + * wrapped by a {@link LazyCsrfTokenRepository}. This is preferred to storing the token in + * a cookie which can be modified by a client application. *
* * @author Rob Winch @@ -82,18 +84,19 @@ public final class CsrfFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - CsrfToken csrfToken = tokenRepository.loadToken(request); + throws ServletException, IOException { + request.setAttribute(HttpServletResponse.class.getName(), response); + + CsrfToken csrfToken = this.tokenRepository.loadToken(request); final boolean missingToken = csrfToken == null; if (missingToken) { - CsrfToken generatedToken = tokenRepository.generateToken(request); - csrfToken = new SaveOnAccessCsrfToken(tokenRepository, request, response, - generatedToken); + csrfToken = this.tokenRepository.generateToken(request); + this.tokenRepository.saveToken(csrfToken, request, response); } request.setAttribute(CsrfToken.class.getName(), csrfToken); request.setAttribute(csrfToken.getParameterName(), csrfToken); - if (!requireCsrfProtectionMatcher.matches(request)) { + if (!this.requireCsrfProtectionMatcher.matches(request)) { filterChain.doFilter(request, response); return; } @@ -103,16 +106,16 @@ public final class CsrfFilter extends OncePerRequestFilter { actualToken = request.getParameter(csrfToken.getParameterName()); } if (!csrfToken.getToken().equals(actualToken)) { - if (logger.isDebugEnabled()) { - logger.debug("Invalid CSRF token found for " + if (this.logger.isDebugEnabled()) { + this.logger.debug("Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)); } if (missingToken) { - accessDeniedHandler.handle(request, response, + this.accessDeniedHandler.handle(request, response, new MissingCsrfTokenException(actualToken)); } else { - accessDeniedHandler.handle(request, response, + this.accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken)); } return; @@ -156,87 +159,9 @@ public final class CsrfFilter extends OncePerRequestFilter { this.accessDeniedHandler = accessDeniedHandler; } - @SuppressWarnings("serial") - private static final class SaveOnAccessCsrfToken implements CsrfToken { - private transient CsrfTokenRepository tokenRepository; - private transient HttpServletRequest request; - private transient HttpServletResponse response; - - private final CsrfToken delegate; - - public SaveOnAccessCsrfToken(CsrfTokenRepository tokenRepository, - HttpServletRequest request, HttpServletResponse response, - CsrfToken delegate) { - super(); - this.tokenRepository = tokenRepository; - this.request = request; - this.response = response; - this.delegate = delegate; - } - - public String getHeaderName() { - return delegate.getHeaderName(); - } - - public String getParameterName() { - return delegate.getParameterName(); - } - - public String getToken() { - saveTokenIfNecessary(); - return delegate.getToken(); - } - - @Override - public String toString() { - return "SaveOnAccessCsrfToken [delegate=" + delegate + "]"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((delegate == null) ? 0 : delegate.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SaveOnAccessCsrfToken other = (SaveOnAccessCsrfToken) obj; - if (delegate == null) { - if (other.delegate != null) - return false; - } - else if (!delegate.equals(other.delegate)) - return false; - return true; - } - - private void saveTokenIfNecessary() { - if (this.tokenRepository == null) { - return; - } - - synchronized (this) { - if (tokenRepository != null) { - this.tokenRepository.saveToken(delegate, request, response); - this.tokenRepository = null; - this.request = null; - this.response = null; - } - } - } - - } - private static final class DefaultRequiresCsrfMatcher implements RequestMatcher { - private final HashSetHttpServletResponse.class.getName()
+ */
+ @Override
+ public CsrfToken generateToken(HttpServletRequest request) {
+ return wrap(request, this.delegate.generateToken(request));
+ }
+
+ /**
+ * Does nothing if the {@link CsrfToken} is not null. Saving is done only when the
+ * {@link CsrfToken#getToken()} iis accessed from
+ * {@link #generateToken(HttpServletRequest)}. If it is null, then the save is
+ * performed immediately.
+ */
+ @Override
+ public void saveToken(CsrfToken token, HttpServletRequest request,
+ HttpServletResponse response) {
+ if (token == null) {
+ this.delegate.saveToken(token, request, response);
+ }
+ }
+
+ /**
+ * Delegates to the injected {@link CsrfTokenRepository}
+ */
+ @Override
+ public CsrfToken loadToken(HttpServletRequest request) {
+ return this.delegate.loadToken(request);
+ }
+
+ private CsrfToken wrap(HttpServletRequest request, CsrfToken token) {
+ HttpServletResponse response = getResponse(request);
+ return new SaveOnAccessCsrfToken(this.delegate, request, response, token);
+ }
+
+ private HttpServletResponse getResponse(HttpServletRequest request) {
+ HttpServletResponse response = (HttpServletResponse) request
+ .getAttribute(HTTP_RESPONSE_ATTR);
+ if (response == null) {
+ throw new IllegalArgumentException(
+ "The HttpServletRequest attribute must contain an HttpServletResponse for the attribute "
+ + HTTP_RESPONSE_ATTR);
+ }
+ return response;
+ }
+
+ private static final class SaveOnAccessCsrfToken implements CsrfToken {
+ private transient CsrfTokenRepository tokenRepository;
+ private transient HttpServletRequest request;
+ private transient HttpServletResponse response;
+
+ private final CsrfToken delegate;
+
+ SaveOnAccessCsrfToken(CsrfTokenRepository tokenRepository,
+ HttpServletRequest request, HttpServletResponse response,
+ CsrfToken delegate) {
+ super();
+ this.tokenRepository = tokenRepository;
+ this.request = request;
+ this.response = response;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String getHeaderName() {
+ return this.delegate.getHeaderName();
+ }
+
+ @Override
+ public String getParameterName() {
+ return this.delegate.getParameterName();
+ }
+
+ @Override
+ public String getToken() {
+ saveTokenIfNecessary();
+ return this.delegate.getToken();
+ }
+
+ @Override
+ public String toString() {
+ return "SaveOnAccessCsrfToken [delegate=" + this.delegate + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((this.delegate == null) ? 0 : this.delegate.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SaveOnAccessCsrfToken other = (SaveOnAccessCsrfToken) obj;
+ if (this.delegate == null) {
+ if (other.delegate != null) {
+ return false;
+ }
+ }
+ else if (!this.delegate.equals(other.delegate)) {
+ return false;
+ }
+ return true;
+ }
+
+ private void saveTokenIfNecessary() {
+ if (this.tokenRepository == null) {
+ return;
+ }
+
+ synchronized (this) {
+ if (this.tokenRepository != null) {
+ this.tokenRepository.saveToken(this.delegate, this.request,
+ this.response);
+ this.tokenRepository = null;
+ this.request = null;
+ this.response = null;
+ }
+ }
+ }
+
+ }
+}
diff --git a/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java b/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java
new file mode 100644
index 0000000000..c0d3ec8e98
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2012-2016 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.web.csrf;
+
+import javax.servlet.http.Cookie;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Rob Winch
+ * @since 4.1
+ */
+public class CookieCsrfTokenRepositoryTests {
+ CookieCsrfTokenRepository repository;
+ MockHttpServletResponse response;
+ MockHttpServletRequest request;
+
+ @Before
+ public void setup() {
+ this.repository = new CookieCsrfTokenRepository();
+ this.request = new MockHttpServletRequest();
+ this.response = new MockHttpServletResponse();
+ this.request.setContextPath("/context");
+ }
+
+ @Test
+ public void generateToken() {
+ CsrfToken generateToken = this.repository.generateToken(this.request);
+
+ assertThat(generateToken).isNotNull();
+ assertThat(generateToken.getHeaderName())
+ .isEqualTo(CookieCsrfTokenRepository.DEFAULT_CSRF_HEADER_NAME);
+ assertThat(generateToken.getParameterName())
+ .isEqualTo(CookieCsrfTokenRepository.DEFAULT_CSRF_PARAMETER_NAME);
+ assertThat(generateToken.getToken()).isNotEmpty();
+ }
+
+ @Test
+ public void generateTokenCustom() {
+ String headerName = "headerName";
+ String parameterName = "paramName";
+ this.repository.setHeaderName(headerName);
+ this.repository.setParameterName(parameterName);
+
+ CsrfToken generateToken = this.repository.generateToken(this.request);
+
+ assertThat(generateToken).isNotNull();
+ assertThat(generateToken.getHeaderName()).isEqualTo(headerName);
+ assertThat(generateToken.getParameterName()).isEqualTo(parameterName);
+ assertThat(generateToken.getToken()).isNotEmpty();
+ }
+
+ @Test
+ public void saveToken() {
+ CsrfToken token = this.repository.generateToken(this.request);
+ this.repository.saveToken(token, this.request, this.response);
+
+ Cookie tokenCookie = this.response
+ .getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
+
+ assertThat(tokenCookie.getMaxAge()).isEqualTo(-1);
+ assertThat(tokenCookie.getName())
+ .isEqualTo(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
+ assertThat(tokenCookie.getPath()).isEqualTo(this.request.getContextPath());
+ assertThat(tokenCookie.getSecure()).isEqualTo(this.request.isSecure());
+ assertThat(tokenCookie.getValue()).isEqualTo(token.getToken());
+ }
+
+ @Test
+ public void saveTokenSecure() {
+ this.request.setSecure(true);
+ CsrfToken token = this.repository.generateToken(this.request);
+ this.repository.saveToken(token, this.request, this.response);
+
+ Cookie tokenCookie = this.response
+ .getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
+
+ assertThat(tokenCookie.getSecure()).isTrue();
+ }
+
+ @Test
+ public void saveTokenNull() {
+ this.request.setSecure(true);
+ this.repository.saveToken(null, this.request, this.response);
+
+ Cookie tokenCookie = this.response
+ .getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
+
+ assertThat(tokenCookie.getMaxAge()).isEqualTo(0);
+ assertThat(tokenCookie.getName())
+ .isEqualTo(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
+ assertThat(tokenCookie.getPath()).isEqualTo(this.request.getContextPath());
+ assertThat(tokenCookie.getSecure()).isEqualTo(this.request.isSecure());
+ assertThat(tokenCookie.getValue()).isEmpty();
+ }
+
+ @Test
+ public void loadTokenNoCookiesNull() {
+ assertThat(this.repository.loadToken(this.request)).isNull();
+ }
+
+ @Test
+ public void loadTokenCookieIncorrectNameNull() {
+ this.request.setCookies(new Cookie("other", "name"));
+
+ assertThat(this.repository.loadToken(this.request)).isNull();
+ }
+
+ @Test
+ public void loadTokenCookieValueEmptyString() {
+ this.request.setCookies(
+ new Cookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, ""));
+
+ assertThat(this.repository.loadToken(this.request)).isNull();
+ }
+
+ @Test
+ public void loadToken() {
+ CsrfToken generateToken = this.repository.generateToken(this.request);
+
+ this.request
+ .setCookies(new Cookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME,
+ generateToken.getToken()));
+
+ CsrfToken loadToken = this.repository.loadToken(this.request);
+
+ assertThat(loadToken).isNotNull();
+ assertThat(loadToken.getHeaderName()).isEqualTo(generateToken.getHeaderName());
+ assertThat(loadToken.getParameterName())
+ .isEqualTo(generateToken.getParameterName());
+ assertThat(loadToken.getToken()).isNotEmpty();
+ }
+
+ @Test
+ public void loadTokenCustom() {
+ String cookieName = "cookieName";
+ String value = "value";
+ String headerName = "headerName";
+ String parameterName = "paramName";
+ this.repository.setHeaderName(headerName);
+ this.repository.setParameterName(parameterName);
+ this.repository.setCookieName(cookieName);
+
+ this.request.setCookies(new Cookie(cookieName, value));
+
+ CsrfToken loadToken = this.repository.loadToken(this.request);
+
+ assertThat(loadToken).isNotNull();
+ assertThat(loadToken.getHeaderName()).isEqualTo(headerName);
+ assertThat(loadToken.getParameterName()).isEqualTo(parameterName);
+ assertThat(loadToken.getToken()).isEqualTo(value);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setCookieNameNullIllegalArgumentException() {
+ this.repository.setCookieName(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setParameterNameNullIllegalArgumentException() {
+ this.repository.setParameterName(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setHeaderNameNullIllegalArgumentException() {
+ this.repository.setHeaderName(null);
+ }
+
+}
diff --git a/web/src/test/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategyTests.java b/web/src/test/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategyTests.java
index 0143a1fcdc..d68949aaa0 100644
--- a/web/src/test/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategyTests.java
+++ b/web/src/test/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategyTests.java
@@ -15,13 +15,6 @@
*/
package org.springframework.security.web.csrf;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -30,10 +23,18 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
+
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
/**
* @author Rob Winch
*
@@ -55,11 +56,12 @@ public class CsrfAuthenticationStrategyTests {
@Before
public void setup() {
- request = new MockHttpServletRequest();
- response = new MockHttpServletResponse();
- strategy = new CsrfAuthenticationStrategy(csrfTokenRepository);
- existingToken = new DefaultCsrfToken("_csrf", "_csrf", "1");
- generatedToken = new DefaultCsrfToken("_csrf", "_csrf", "2");
+ this.response = new MockHttpServletResponse();
+ this.request = new MockHttpServletRequest();
+ this.request.setAttribute(HttpServletResponse.class.getName(), this.response);
+ this.strategy = new CsrfAuthenticationStrategy(this.csrfTokenRepository);
+ this.existingToken = new DefaultCsrfToken("_csrf", "_csrf", "1");
+ this.generatedToken = new DefaultCsrfToken("_csrf", "_csrf", "2");
}
@Test(expected = IllegalArgumentException.class)
@@ -69,51 +71,61 @@ public class CsrfAuthenticationStrategyTests {
@Test
public void logoutRemovesCsrfTokenAndSavesNew() {
- when(csrfTokenRepository.loadToken(request)).thenReturn(existingToken);
- when(csrfTokenRepository.generateToken(request)).thenReturn(generatedToken);
- strategy.onAuthentication(new TestingAuthenticationToken("user", "password",
- "ROLE_USER"), request, response);
+ when(this.csrfTokenRepository.loadToken(this.request))
+ .thenReturn(this.existingToken);
+ when(this.csrfTokenRepository.generateToken(this.request))
+ .thenReturn(this.generatedToken);
+ this.strategy.onAuthentication(
+ new TestingAuthenticationToken("user", "password", "ROLE_USER"),
+ this.request, this.response);
- verify(csrfTokenRepository).saveToken(null, request, response);
- verify(csrfTokenRepository, never()).saveToken(eq(generatedToken),
+ verify(this.csrfTokenRepository).saveToken(null, this.request, this.response);
+ verify(this.csrfTokenRepository).saveToken(eq(this.generatedToken),
any(HttpServletRequest.class), any(HttpServletResponse.class));
// SEC-2404, SEC-2832
- CsrfToken tokenInRequest = (CsrfToken) request.getAttribute(CsrfToken.class
- .getName());
- assertThat(tokenInRequest.getToken()).isSameAs(generatedToken.getToken());
- assertThat(tokenInRequest.getHeaderName()).isSameAs(
- generatedToken.getHeaderName());
- assertThat(tokenInRequest.getParameterName()).isSameAs(
- generatedToken.getParameterName());
- assertThat(request.getAttribute(generatedToken.getParameterName())).isSameAs(
- tokenInRequest);
+ CsrfToken tokenInRequest = (CsrfToken) this.request
+ .getAttribute(CsrfToken.class.getName());
+ assertThat(tokenInRequest.getToken()).isSameAs(this.generatedToken.getToken());
+ assertThat(tokenInRequest.getHeaderName())
+ .isSameAs(this.generatedToken.getHeaderName());
+ assertThat(tokenInRequest.getParameterName())
+ .isSameAs(this.generatedToken.getParameterName());
+ assertThat(this.request.getAttribute(this.generatedToken.getParameterName()))
+ .isSameAs(tokenInRequest);
}
// SEC-2872
@Test
public void delaySavingCsrf() {
- when(csrfTokenRepository.loadToken(request)).thenReturn(existingToken);
- when(csrfTokenRepository.generateToken(request)).thenReturn(generatedToken);
- strategy.onAuthentication(new TestingAuthenticationToken("user", "password",
- "ROLE_USER"), request, response);
+ this.strategy = new CsrfAuthenticationStrategy(
+ new LazyCsrfTokenRepository(this.csrfTokenRepository));
- verify(csrfTokenRepository).saveToken(null, request, response);
- verify(csrfTokenRepository, never()).saveToken(eq(generatedToken),
+ when(this.csrfTokenRepository.loadToken(this.request))
+ .thenReturn(this.existingToken);
+ when(this.csrfTokenRepository.generateToken(this.request))
+ .thenReturn(this.generatedToken);
+ this.strategy.onAuthentication(
+ new TestingAuthenticationToken("user", "password", "ROLE_USER"),
+ this.request, this.response);
+
+ verify(this.csrfTokenRepository).saveToken(null, this.request, this.response);
+ verify(this.csrfTokenRepository, never()).saveToken(eq(this.generatedToken),
any(HttpServletRequest.class), any(HttpServletResponse.class));
- CsrfToken tokenInRequest = (CsrfToken) request.getAttribute(CsrfToken.class
- .getName());
+ CsrfToken tokenInRequest = (CsrfToken) this.request
+ .getAttribute(CsrfToken.class.getName());
tokenInRequest.getToken();
- verify(csrfTokenRepository).saveToken(eq(generatedToken),
+ verify(this.csrfTokenRepository).saveToken(eq(this.generatedToken),
any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void logoutRemovesNoActionIfNullToken() {
- strategy.onAuthentication(new TestingAuthenticationToken("user", "password",
- "ROLE_USER"), request, response);
+ this.strategy.onAuthentication(
+ new TestingAuthenticationToken("user", "password", "ROLE_USER"),
+ this.request, this.response);
- verify(csrfTokenRepository, never()).saveToken(any(CsrfToken.class),
+ verify(this.csrfTokenRepository, never()).saveToken(any(CsrfToken.class),
any(HttpServletRequest.class), any(HttpServletResponse.class));
}
}
diff --git a/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java b/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java
index 5fe68038a7..970276788c 100644
--- a/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java
+++ b/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java
@@ -15,14 +15,6 @@
*/
package org.springframework.security.web.csrf;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
import java.io.IOException;
import java.util.Arrays;
@@ -38,11 +30,21 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
+
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.util.matcher.RequestMatcher;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
/**
* @author Rob Winch
*
@@ -67,16 +69,21 @@ public class CsrfFilterTests {
@Before
public void setup() {
- token = new DefaultCsrfToken("headerName", "paramName", "csrfTokenValue");
+ this.token = new DefaultCsrfToken("headerName", "paramName", "csrfTokenValue");
resetRequestResponse();
- filter = new CsrfFilter(tokenRepository);
- filter.setRequireCsrfProtectionMatcher(requestMatcher);
- filter.setAccessDeniedHandler(deniedHandler);
+ this.filter = createCsrfFilter(this.tokenRepository);
+ }
+
+ private CsrfFilter createCsrfFilter(CsrfTokenRepository repository) {
+ CsrfFilter filter = new CsrfFilter(repository);
+ filter.setRequireCsrfProtectionMatcher(this.requestMatcher);
+ filter.setAccessDeniedHandler(this.deniedHandler);
+ return filter;
}
private void resetRequestResponse() {
- request = new MockHttpServletRequest();
- response = new MockHttpServletResponse();
+ this.request = new MockHttpServletRequest();
+ this.response = new MockHttpServletResponse();
}
@Test(expected = IllegalArgumentException.class)
@@ -86,282 +93,319 @@ public class CsrfFilterTests {
// SEC-2276
@Test
- public void doFilterDoesNotSaveCsrfTokenUntilAccessed() throws ServletException,
- IOException {
- when(requestMatcher.matches(request)).thenReturn(false);
- when(tokenRepository.generateToken(request)).thenReturn(token);
+ public void doFilterDoesNotSaveCsrfTokenUntilAccessed()
+ throws ServletException, IOException {
+ this.filter = createCsrfFilter(new LazyCsrfTokenRepository(this.tokenRepository));
+ when(this.requestMatcher.matches(this.request)).thenReturn(false);
+ when(this.tokenRepository.generateToken(this.request)).thenReturn(this.token);
- filter.doFilter(request, response, filterChain);
- CsrfToken attrToken = (CsrfToken) request.getAttribute(token.getParameterName());
+ this.filter.doFilter(this.request, this.response, this.filterChain);
+ CsrfToken attrToken = (CsrfToken) this.request
+ .getAttribute(this.token.getParameterName());
// no CsrfToken should have been saved yet
- verify(tokenRepository, times(0)).saveToken(any(CsrfToken.class),
+ verify(this.tokenRepository, times(0)).saveToken(any(CsrfToken.class),
any(HttpServletRequest.class), any(HttpServletResponse.class));
- verify(filterChain).doFilter(request, response);
+ verify(this.filterChain).doFilter(this.request, this.response);
// access the token
attrToken.getToken();
// now the CsrfToken should have been saved
- verify(tokenRepository).saveToken(eq(token), any(HttpServletRequest.class),
- any(HttpServletResponse.class));
+ verify(this.tokenRepository).saveToken(eq(this.token),
+ any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
- public void doFilterAccessDeniedNoTokenPresent() throws ServletException, IOException {
- when(requestMatcher.matches(request)).thenReturn(true);
- when(tokenRepository.loadToken(request)).thenReturn(token);
+ public void doFilterAccessDeniedNoTokenPresent()
+ throws ServletException, IOException {
+ when(this.requestMatcher.matches(this.request)).thenReturn(true);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertThat(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(deniedHandler).handle(eq(request), eq(response),
+ verify(this.deniedHandler).handle(eq(this.request), eq(this.response),
any(InvalidCsrfTokenException.class));
- verifyZeroInteractions(filterChain);
+ verifyZeroInteractions(this.filterChain);
}
@Test
- public void doFilterAccessDeniedIncorrectTokenPresent() throws ServletException,
- IOException {
- when(requestMatcher.matches(request)).thenReturn(true);
- when(tokenRepository.loadToken(request)).thenReturn(token);
- request.setParameter(token.getParameterName(), token.getToken() + " INVALID");
+ public void doFilterAccessDeniedIncorrectTokenPresent()
+ throws ServletException, IOException {
+ when(this.requestMatcher.matches(this.request)).thenReturn(true);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
+ this.request.setParameter(this.token.getParameterName(),
+ this.token.getToken() + " INVALID");
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertThat(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(deniedHandler).handle(eq(request), eq(response),
+ verify(this.deniedHandler).handle(eq(this.request), eq(this.response),
any(InvalidCsrfTokenException.class));
- verifyZeroInteractions(filterChain);
+ verifyZeroInteractions(this.filterChain);
}
@Test
public void doFilterAccessDeniedIncorrectTokenPresentHeader()
throws ServletException, IOException {
- when(requestMatcher.matches(request)).thenReturn(true);
- when(tokenRepository.loadToken(request)).thenReturn(token);
- request.addHeader(token.getHeaderName(), token.getToken() + " INVALID");
+ when(this.requestMatcher.matches(this.request)).thenReturn(true);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
+ this.request.addHeader(this.token.getHeaderName(),
+ this.token.getToken() + " INVALID");
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertThat(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(deniedHandler).handle(eq(request), eq(response),
+ verify(this.deniedHandler).handle(eq(this.request), eq(this.response),
any(InvalidCsrfTokenException.class));
- verifyZeroInteractions(filterChain);
+ verifyZeroInteractions(this.filterChain);
}
@Test
public void doFilterAccessDeniedIncorrectTokenPresentHeaderPreferredOverParameter()
throws ServletException, IOException {
- when(requestMatcher.matches(request)).thenReturn(true);
- when(tokenRepository.loadToken(request)).thenReturn(token);
- request.setParameter(token.getParameterName(), token.getToken());
- request.addHeader(token.getHeaderName(), token.getToken() + " INVALID");
+ when(this.requestMatcher.matches(this.request)).thenReturn(true);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
+ this.request.setParameter(this.token.getParameterName(), this.token.getToken());
+ this.request.addHeader(this.token.getHeaderName(),
+ this.token.getToken() + " INVALID");
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertThat(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(deniedHandler).handle(eq(request), eq(response),
+ verify(this.deniedHandler).handle(eq(this.request), eq(this.response),
any(InvalidCsrfTokenException.class));
- verifyZeroInteractions(filterChain);
+ verifyZeroInteractions(this.filterChain);
}
@Test
- public void doFilterNotCsrfRequestExistingToken() throws ServletException,
- IOException {
- when(requestMatcher.matches(request)).thenReturn(false);
- when(tokenRepository.loadToken(request)).thenReturn(token);
+ public void doFilterNotCsrfRequestExistingToken()
+ throws ServletException, IOException {
+ when(this.requestMatcher.matches(this.request)).thenReturn(false);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertThat(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(filterChain).doFilter(request, response);
- verifyZeroInteractions(deniedHandler);
+ verify(this.filterChain).doFilter(this.request, this.response);
+ verifyZeroInteractions(this.deniedHandler);
}
@Test
- public void doFilterNotCsrfRequestGenerateToken() throws ServletException,
- IOException {
- when(requestMatcher.matches(request)).thenReturn(false);
- when(tokenRepository.generateToken(request)).thenReturn(token);
+ public void doFilterNotCsrfRequestGenerateToken()
+ throws ServletException, IOException {
+ when(this.requestMatcher.matches(this.request)).thenReturn(false);
+ when(this.tokenRepository.generateToken(this.request)).thenReturn(this.token);
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertToken(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertToken(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertToken(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertToken(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(filterChain).doFilter(request, response);
- verifyZeroInteractions(deniedHandler);
+ verify(this.filterChain).doFilter(this.request, this.response);
+ verifyZeroInteractions(this.deniedHandler);
}
@Test
- public void doFilterIsCsrfRequestExistingTokenHeader() throws ServletException,
- IOException {
- when(requestMatcher.matches(request)).thenReturn(true);
- when(tokenRepository.loadToken(request)).thenReturn(token);
- request.addHeader(token.getHeaderName(), token.getToken());
+ public void doFilterIsCsrfRequestExistingTokenHeader()
+ throws ServletException, IOException {
+ when(this.requestMatcher.matches(this.request)).thenReturn(true);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
+ this.request.addHeader(this.token.getHeaderName(), this.token.getToken());
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertThat(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(filterChain).doFilter(request, response);
- verifyZeroInteractions(deniedHandler);
+ verify(this.filterChain).doFilter(this.request, this.response);
+ verifyZeroInteractions(this.deniedHandler);
}
@Test
public void doFilterIsCsrfRequestExistingTokenHeaderPreferredOverInvalidParam()
throws ServletException, IOException {
- when(requestMatcher.matches(request)).thenReturn(true);
- when(tokenRepository.loadToken(request)).thenReturn(token);
- request.setParameter(token.getParameterName(), token.getToken() + " INVALID");
- request.addHeader(token.getHeaderName(), token.getToken());
+ when(this.requestMatcher.matches(this.request)).thenReturn(true);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
+ this.request.setParameter(this.token.getParameterName(),
+ this.token.getToken() + " INVALID");
+ this.request.addHeader(this.token.getHeaderName(), this.token.getToken());
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertThat(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(filterChain).doFilter(request, response);
- verifyZeroInteractions(deniedHandler);
+ verify(this.filterChain).doFilter(this.request, this.response);
+ verifyZeroInteractions(this.deniedHandler);
}
@Test
- public void doFilterIsCsrfRequestExistingToken() throws ServletException, IOException {
- when(requestMatcher.matches(request)).thenReturn(true);
- when(tokenRepository.loadToken(request)).thenReturn(token);
- request.setParameter(token.getParameterName(), token.getToken());
+ public void doFilterIsCsrfRequestExistingToken()
+ throws ServletException, IOException {
+ when(this.requestMatcher.matches(this.request)).thenReturn(true);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
+ this.request.setParameter(this.token.getParameterName(), this.token.getToken());
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertThat(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(filterChain).doFilter(request, response);
- verifyZeroInteractions(deniedHandler);
+ verify(this.filterChain).doFilter(this.request, this.response);
+ verifyZeroInteractions(this.deniedHandler);
+ verify(this.tokenRepository, never()).saveToken(any(CsrfToken.class),
+ any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
- public void doFilterIsCsrfRequestGenerateToken() throws ServletException, IOException {
- when(requestMatcher.matches(request)).thenReturn(true);
- when(tokenRepository.generateToken(request)).thenReturn(token);
- request.setParameter(token.getParameterName(), token.getToken());
+ public void doFilterIsCsrfRequestGenerateToken()
+ throws ServletException, IOException {
+ when(this.requestMatcher.matches(this.request)).thenReturn(true);
+ when(this.tokenRepository.generateToken(this.request)).thenReturn(this.token);
+ this.request.setParameter(this.token.getParameterName(), this.token.getToken());
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertToken(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertToken(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertToken(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertToken(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- verify(filterChain).doFilter(request, response);
- verifyZeroInteractions(deniedHandler);
+ // LazyCsrfTokenRepository requires the response as an attribute
+ assertThat(this.request.getAttribute(HttpServletResponse.class.getName()))
+ .isEqualTo(this.response);
+
+ verify(this.filterChain).doFilter(this.request, this.response);
+ verify(this.tokenRepository).saveToken(this.token, this.request, this.response);
+ verifyZeroInteractions(this.deniedHandler);
}
@Test
public void doFilterDefaultRequireCsrfProtectionMatcherAllowedMethods()
throws ServletException, IOException {
- filter = new CsrfFilter(tokenRepository);
- filter.setAccessDeniedHandler(deniedHandler);
+ this.filter = new CsrfFilter(this.tokenRepository);
+ this.filter.setAccessDeniedHandler(this.deniedHandler);
for (String method : Arrays.asList("GET", "TRACE", "OPTIONS", "HEAD")) {
resetRequestResponse();
- when(tokenRepository.loadToken(request)).thenReturn(token);
- request.setMethod(method);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
+ this.request.setMethod(method);
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- verify(filterChain).doFilter(request, response);
- verifyZeroInteractions(deniedHandler);
+ verify(this.filterChain).doFilter(this.request, this.response);
+ verifyZeroInteractions(this.deniedHandler);
}
}
/**
* SEC-2292 Should not allow other cases through since spec states HTTP method is case
* sensitive http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1
+ * @throws Exception if an error occurs
*
- * @throws ServletException
- * @throws IOException
*/
@Test
public void doFilterDefaultRequireCsrfProtectionMatcherAllowedMethodsCaseSensitive()
- throws ServletException, IOException {
- filter = new CsrfFilter(tokenRepository);
- filter.setAccessDeniedHandler(deniedHandler);
+ throws Exception {
+ this.filter = new CsrfFilter(this.tokenRepository);
+ this.filter.setAccessDeniedHandler(this.deniedHandler);
for (String method : Arrays.asList("get", "TrAcE", "oPTIOnS", "hEaD")) {
resetRequestResponse();
- when(tokenRepository.loadToken(request)).thenReturn(token);
- request.setMethod(method);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
+ this.request.setMethod(method);
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- verify(deniedHandler).handle(eq(request), eq(response),
+ verify(this.deniedHandler).handle(eq(this.request), eq(this.response),
any(InvalidCsrfTokenException.class));
- verifyZeroInteractions(filterChain);
+ verifyZeroInteractions(this.filterChain);
}
}
@Test
public void doFilterDefaultRequireCsrfProtectionMatcherDeniedMethods()
throws ServletException, IOException {
- filter = new CsrfFilter(tokenRepository);
- filter.setAccessDeniedHandler(deniedHandler);
+ this.filter = new CsrfFilter(this.tokenRepository);
+ this.filter.setAccessDeniedHandler(this.deniedHandler);
for (String method : Arrays.asList("POST", "PUT", "PATCH", "DELETE", "INVALID")) {
resetRequestResponse();
- when(tokenRepository.loadToken(request)).thenReturn(token);
- request.setMethod(method);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
+ this.request.setMethod(method);
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- verify(deniedHandler).handle(eq(request), eq(response),
+ verify(this.deniedHandler).handle(eq(this.request), eq(this.response),
any(InvalidCsrfTokenException.class));
- verifyZeroInteractions(filterChain);
+ verifyZeroInteractions(this.filterChain);
}
}
@Test
public void doFilterDefaultAccessDenied() throws ServletException, IOException {
- filter = new CsrfFilter(tokenRepository);
- filter.setRequireCsrfProtectionMatcher(requestMatcher);
- when(requestMatcher.matches(request)).thenReturn(true);
- when(tokenRepository.loadToken(request)).thenReturn(token);
+ this.filter = new CsrfFilter(this.tokenRepository);
+ this.filter.setRequireCsrfProtectionMatcher(this.requestMatcher);
+ when(this.requestMatcher.matches(this.request)).thenReturn(true);
+ when(this.tokenRepository.loadToken(this.request)).thenReturn(this.token);
- filter.doFilter(request, response, filterChain);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
- assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
- assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
+ assertThat(this.request.getAttribute(this.token.getParameterName()))
+ .isEqualTo(this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName()))
+ .isEqualTo(this.token);
- assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
- verifyZeroInteractions(filterChain);
+ assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
+ verifyZeroInteractions(this.filterChain);
}
@Test(expected = IllegalArgumentException.class)
public void setRequireCsrfProtectionMatcherNull() {
- filter.setRequireCsrfProtectionMatcher(null);
+ this.filter.setRequireCsrfProtectionMatcher(null);
}
@Test(expected = IllegalArgumentException.class)
public void setAccessDeniedHandlerNull() {
- filter.setAccessDeniedHandler(null);
+ this.filter.setAccessDeniedHandler(null);
}
private static final CsrfTokenAssert assertToken(Object token) {
return new CsrfTokenAssert((CsrfToken) token);
}
- private static class CsrfTokenAssert extends
- AbstractObjectAssert