SEC-1574: Add CSRF Support
This commit is contained in:
+48
-23
@@ -28,6 +28,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.util.RequestMatcher;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -49,7 +50,9 @@ public class LogoutFilter extends GenericFilterBean {
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private String filterProcessesUrl = "/j_spring_security_logout";
|
||||
private String filterProcessesUrl;
|
||||
private RequestMatcher logoutRequestMatcher;
|
||||
|
||||
private final List<LogoutHandler> handlers;
|
||||
private final LogoutSuccessHandler logoutSuccessHandler;
|
||||
|
||||
@@ -65,6 +68,7 @@ public class LogoutFilter extends GenericFilterBean {
|
||||
this.handlers = Arrays.asList(handlers);
|
||||
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
|
||||
this.logoutSuccessHandler = logoutSuccessHandler;
|
||||
setFilterProcessesUrl("/j_spring_security_logout");
|
||||
}
|
||||
|
||||
public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
|
||||
@@ -77,6 +81,7 @@ public class LogoutFilter extends GenericFilterBean {
|
||||
urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
|
||||
}
|
||||
logoutSuccessHandler = urlLogoutSuccessHandler;
|
||||
setFilterProcessesUrl("/j_spring_security_logout");
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
@@ -114,35 +119,55 @@ public class LogoutFilter extends GenericFilterBean {
|
||||
* @return <code>true</code> if logout should occur, <code>false</code> otherwise
|
||||
*/
|
||||
protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) {
|
||||
String uri = request.getRequestURI();
|
||||
int pathParamIndex = uri.indexOf(';');
|
||||
|
||||
if (pathParamIndex > 0) {
|
||||
// strip everything from the first semi-colon
|
||||
uri = uri.substring(0, pathParamIndex);
|
||||
}
|
||||
|
||||
int queryParamIndex = uri.indexOf('?');
|
||||
|
||||
if (queryParamIndex > 0) {
|
||||
// strip everything from the first question mark
|
||||
uri = uri.substring(0, queryParamIndex);
|
||||
}
|
||||
|
||||
if ("".equals(request.getContextPath())) {
|
||||
return uri.endsWith(filterProcessesUrl);
|
||||
}
|
||||
|
||||
return uri.endsWith(request.getContextPath() + filterProcessesUrl);
|
||||
return logoutRequestMatcher.matches(request);
|
||||
}
|
||||
|
||||
public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
|
||||
Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null");
|
||||
this.logoutRequestMatcher = logoutRequestMatcher;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setFilterProcessesUrl(String filterProcessesUrl) {
|
||||
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid value for" +
|
||||
" 'filterProcessesUrl'");
|
||||
this.logoutRequestMatcher = new FilterProcessUrlRequestMatcher(filterProcessesUrl);
|
||||
this.filterProcessesUrl = filterProcessesUrl;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected String getFilterProcessesUrl() {
|
||||
return filterProcessesUrl;
|
||||
}
|
||||
|
||||
private static final class FilterProcessUrlRequestMatcher implements RequestMatcher {
|
||||
private final String filterProcessesUrl;
|
||||
|
||||
private FilterProcessUrlRequestMatcher(String filterProcessesUrl) {
|
||||
Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
|
||||
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
|
||||
this.filterProcessesUrl = filterProcessesUrl;
|
||||
}
|
||||
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
String uri = request.getRequestURI();
|
||||
int pathParamIndex = uri.indexOf(';');
|
||||
|
||||
if (pathParamIndex > 0) {
|
||||
// strip everything from the first semi-colon
|
||||
uri = uri.substring(0, pathParamIndex);
|
||||
}
|
||||
|
||||
int queryParamIndex = uri.indexOf('?');
|
||||
|
||||
if (queryParamIndex > 0) {
|
||||
// strip everything from the first question mark
|
||||
uri = uri.substring(0, queryParamIndex);
|
||||
}
|
||||
|
||||
if ("".equals(request.getContextPath())) {
|
||||
return uri.endsWith(filterProcessesUrl);
|
||||
}
|
||||
|
||||
return uri.endsWith(request.getContextPath() + filterProcessesUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ public class CompositeSessionAuthenticationStrategy implements SessionAuthentica
|
||||
throw new IllegalArgumentException("delegateStrategies cannot contain null entires. Got " + delegateStrategies);
|
||||
}
|
||||
}
|
||||
this.delegateStrategies = new ArrayList<SessionAuthenticationStrategy>(delegateStrategies);
|
||||
this.delegateStrategies = delegateStrategies;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
||||
+11
@@ -27,6 +27,7 @@ import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.WebAttributes;
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
/**
|
||||
@@ -164,6 +165,7 @@ public class DefaultLoginPageViewFilter extends GenericFilterBean {
|
||||
}
|
||||
|
||||
sb.append(" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n");
|
||||
renderHiddenInputs(sb, request);
|
||||
sb.append(" </table>\n");
|
||||
sb.append("</form>");
|
||||
}
|
||||
@@ -181,6 +183,7 @@ public class DefaultLoginPageViewFilter extends GenericFilterBean {
|
||||
|
||||
sb.append(" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n");
|
||||
sb.append(" </table>\n");
|
||||
renderHiddenInputs(sb, request);
|
||||
sb.append("</form>");
|
||||
}
|
||||
|
||||
@@ -189,6 +192,14 @@ public class DefaultLoginPageViewFilter extends GenericFilterBean {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void renderHiddenInputs(StringBuilder sb, HttpServletRequest request) {
|
||||
CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
|
||||
|
||||
if(token != null) {
|
||||
sb.append(" <input name=\""+ token.getParameterName() +"\" type=\"hidden\" value=\""+ token.getToken() +"\" />\n");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLogoutSuccess(HttpServletRequest request) {
|
||||
return logoutSuccessUrl != null && matches(request, logoutSuccessUrl);
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link CsrfAuthenticationStrategy} is in charge of removing the {@link CsrfToken} upon
|
||||
* authenticating. A new {@link CsrfToken} will then be generated by the framework upon
|
||||
* the next request.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class CsrfAuthenticationStrategy implements
|
||||
SessionAuthenticationStrategy {
|
||||
|
||||
private final CsrfTokenRepository csrfTokenRepository;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
|
||||
*/
|
||||
public CsrfAuthenticationStrategy(CsrfTokenRepository csrfTokenRepository) {
|
||||
Assert.notNull(csrfTokenRepository,"csrfTokenRepository cannot be null");
|
||||
this.csrfTokenRepository = csrfTokenRepository;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.security.web.authentication.session.SessionAuthenticationStrategy#onAuthentication(org.springframework.security.core.Authentication, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
public void onAuthentication(Authentication authentication,
|
||||
HttpServletRequest request, HttpServletResponse response)
|
||||
throws SessionAuthenticationException {
|
||||
this.csrfTokenRepository.saveToken(null, request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
|
||||
import org.springframework.security.web.util.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Applies <a
|
||||
* href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)"
|
||||
* >CSRF</a> 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).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class CsrfFilter extends OncePerRequestFilter {
|
||||
private final CsrfTokenRepository tokenRepository;
|
||||
private RequestMatcher requireCsrfProtectionMatcher = new DefaultRequiresCsrfMatcher();
|
||||
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
|
||||
|
||||
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
|
||||
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
|
||||
this.tokenRepository = csrfTokenRepository;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)
|
||||
*/
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
CsrfToken csrfToken = tokenRepository.loadToken(request);
|
||||
if(csrfToken == null) {
|
||||
csrfToken = tokenRepository.generateAndSaveToken(request, response);
|
||||
}
|
||||
request.setAttribute(CsrfToken.class.getName(), csrfToken);
|
||||
request.setAttribute(csrfToken.getParameterName(), csrfToken);
|
||||
response.addHeader(csrfToken.getHeaderName(), csrfToken.getToken());
|
||||
|
||||
if(!requireCsrfProtectionMatcher.matches(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String actualToken = request.getHeader(csrfToken.getHeaderName());
|
||||
if(actualToken == null) {
|
||||
actualToken = request.getParameter(csrfToken.getParameterName());
|
||||
}
|
||||
if(!csrfToken.getToken().equals(actualToken)) {
|
||||
accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken));
|
||||
return;
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a {@link RequestMatcher} that is used to determine if CSRF
|
||||
* protection should be applied. If the {@link RequestMatcher} returns true
|
||||
* for a given request, then CSRF protection is applied.
|
||||
*
|
||||
* <p>
|
||||
* The default is to apply CSRF protection for any HTTP method other than
|
||||
* GET, HEAD, TRACE, OPTIONS.
|
||||
* </p>
|
||||
*
|
||||
* @param requireCsrfProtectionMatcher
|
||||
* the {@link RequestMatcher} used to determine if CSRF
|
||||
* protection should be applied.
|
||||
*/
|
||||
public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) {
|
||||
Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null");
|
||||
this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specifies a {@link AccessDeniedHandler} that should be used when CSRF protection fails.
|
||||
*
|
||||
* <p>
|
||||
* The default is to use AccessDeniedHandlerImpl with no arguments.
|
||||
* </p>
|
||||
*
|
||||
* @param accessDeniedHandler
|
||||
* the {@link AccessDeniedHandler} to use
|
||||
*/
|
||||
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
|
||||
Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
|
||||
this.accessDeniedHandler = accessDeniedHandler;
|
||||
}
|
||||
|
||||
private static class DefaultRequiresCsrfMatcher implements RequestMatcher {
|
||||
private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.security.web.util.RequestMatcher#matches(javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
return !allowedMethods.matcher(request.getMethod()).matches();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link CsrfLogoutHandler} is in charge of removing the {@link CsrfToken} upon
|
||||
* logout. A new {@link CsrfToken} will then be generated by the framework upon
|
||||
* the next request.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class CsrfLogoutHandler implements LogoutHandler {
|
||||
private final CsrfTokenRepository csrfTokenRepository;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
|
||||
*/
|
||||
public CsrfLogoutHandler(CsrfTokenRepository csrfTokenRepository) {
|
||||
Assert.notNull(csrfTokenRepository,"csrfTokenRepository cannot be null");
|
||||
this.csrfTokenRepository = csrfTokenRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the {@link CsrfToken}
|
||||
*
|
||||
* @see org.springframework.security.web.authentication.logout.LogoutHandler#logout(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.security.core.Authentication)
|
||||
*/
|
||||
public void logout(HttpServletRequest request,
|
||||
HttpServletResponse response, Authentication authentication) {
|
||||
this.csrfTokenRepository.saveToken(null, request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A CSRF token that is used to protect against CSRF attacks.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public final class CsrfToken implements Serializable {
|
||||
|
||||
private final String token;
|
||||
|
||||
private final String parameterName;
|
||||
|
||||
private final String headerName;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param headerName the HTTP header name to use
|
||||
* @param parameterName the HTTP parameter name to use
|
||||
* @param token the value of the token (i.e. expected value of the HTTP parameter of parametername).
|
||||
*/
|
||||
public CsrfToken(String headerName, String parameterName, String token) {
|
||||
Assert.hasLength(headerName, "headerName cannot be null or empty");
|
||||
Assert.hasLength(parameterName, "parameterName cannot be null or empty");
|
||||
Assert.hasLength(token, "token cannot be null or empty");
|
||||
this.headerName = headerName;
|
||||
this.parameterName = parameterName;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTTP header that the CSRF is populated on the response and can
|
||||
* be placed on requests instead of the parameter. Cannot be null.
|
||||
*
|
||||
* @return the HTTP header that the CSRF is populated on the response and
|
||||
* can be placed on requests instead of the parameter
|
||||
*/
|
||||
public String getHeaderName() {
|
||||
return headerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTTP parameter name that should contain the token. Cannot be null.
|
||||
* @return the HTTP parameter name that should contain the token.
|
||||
*/
|
||||
public String getParameterName() {
|
||||
return parameterName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token value. Cannot be null.
|
||||
* @return the token value
|
||||
*/
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* An API to allow changing the method in which the expected {@link CsrfToken}
|
||||
* is associated to the {@link HttpServletRequest}. For example, it may be
|
||||
* stored in {@link HttpSession}.
|
||||
*
|
||||
* @see HttpSessionCsrfTokenRepository
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*
|
||||
*/
|
||||
public interface CsrfTokenRepository {
|
||||
|
||||
/**
|
||||
* Generates and saves the expected {@link CsrfToken}
|
||||
*
|
||||
* @param request
|
||||
* the {@link HttpServletRequest} to use
|
||||
* @param response
|
||||
* the {@link HttpServletResponse} to use
|
||||
* @return the {@link CsrfToken} that was generated and saved. Cannot be
|
||||
* null.
|
||||
*/
|
||||
CsrfToken generateAndSaveToken(HttpServletRequest request,
|
||||
HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* Saves the {@link CsrfToken} using the {@link HttpServletRequest} and
|
||||
* {@link HttpServletResponse}. If the {@link CsrfToken} is null, it is the
|
||||
* same as deleting it.
|
||||
*
|
||||
* @param token
|
||||
* the {@link CsrfToken} to save or null to delete
|
||||
* @param request
|
||||
* the {@link HttpServletRequest} to use
|
||||
* @param response
|
||||
* the {@link HttpServletResponse} to use
|
||||
*/
|
||||
void saveToken(CsrfToken token, HttpServletRequest request,
|
||||
HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* Loads the expected {@link CsrfToken} from the {@link HttpServletRequest}
|
||||
*
|
||||
* @param request
|
||||
* the {@link HttpServletRequest} to use
|
||||
* @return the {@link CsrfToken} or null if none exists
|
||||
*/
|
||||
CsrfToken loadToken(HttpServletRequest request);
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link CsrfTokenRepository} that stores the {@link CsrfToken} in the {@link HttpSession}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {
|
||||
private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";
|
||||
|
||||
private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";
|
||||
|
||||
private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN");
|
||||
|
||||
private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;
|
||||
|
||||
private String headerName = DEFAULT_CSRF_HEADER_NAME;
|
||||
|
||||
private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.security.web.csrf.CsrfTokenRepository#saveToken(org.springframework.security.web.csrf.CsrfToken, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
public void saveToken(CsrfToken token, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
HttpSession session = request.getSession();
|
||||
if(token == null) {
|
||||
session.removeAttribute(sessionAttributeName);
|
||||
} else {
|
||||
session.setAttribute(sessionAttributeName, token);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.security.web.csrf.CsrfTokenRepository#loadToken(javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
public CsrfToken loadToken(HttpServletRequest request) {
|
||||
return (CsrfToken) request.getSession().getAttribute(sessionAttributeName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.security.web.csrf.CsrfTokenRepository#generateNewToken(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
public CsrfToken generateAndSaveToken(HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
CsrfToken token = new CsrfToken(headerName, parameterName, createNewToken());
|
||||
saveToken(token, request, response);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link HttpServletRequest} parameter name that the {@link CsrfToken} is expected to appear on
|
||||
* @param parameterName the new parameter name to use
|
||||
*/
|
||||
public void setParameterName(String parameterName) {
|
||||
Assert.hasLength(parameterName, "parameterName cannot be null or empty");
|
||||
this.parameterName = parameterName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the header name that the {@link CsrfToken} is expected to appear on
|
||||
* and the header that the response will contain the {@link CsrfToken}.
|
||||
*
|
||||
* @param parameterName
|
||||
* the new parameter name to use
|
||||
*/
|
||||
public void setHeaderName(String parameterName) {
|
||||
Assert.hasLength(parameterName, "parameterName cannot be null or empty");
|
||||
this.parameterName = parameterName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link HttpSession} attribute name that the {@link CsrfToken} is stored in
|
||||
* @param sessionAttributeName the new attribute name to use
|
||||
*/
|
||||
public void setSessionAttributeName(String sessionAttributeName) {
|
||||
Assert.hasLength(sessionAttributeName, "sessionAttributename cannot be null or empty");
|
||||
this.sessionAttributeName = sessionAttributeName;
|
||||
}
|
||||
|
||||
private String createNewToken() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown when an invalid or missing {@link CsrfToken} is found in the HttpServletRequest
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class InvalidCsrfTokenException extends AccessDeniedException {
|
||||
|
||||
/**
|
||||
* @param msg
|
||||
*/
|
||||
public InvalidCsrfTokenException(CsrfToken expectedAccessToken, String actualAccessToken) {
|
||||
super("Invalid CSRF Token '" + actualAccessToken
|
||||
+ "' was found on the request parameter '"
|
||||
+ expectedAccessToken.getParameterName() + "' or header '"
|
||||
+ expectedAccessToken.getHeaderName() + "'.");
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.web.servlet.support.csrf;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.web.servlet.support.RequestDataValueProcessor;
|
||||
|
||||
/**
|
||||
* Integration with Spring Web MVC that automatically adds the {@link CsrfToken}
|
||||
* into forms with hidden inputs when using Spring tag libraries.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class CsrfRequestDataValueProcessor implements
|
||||
RequestDataValueProcessor {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.web.servlet.support.RequestDataValueProcessor#
|
||||
* processAction(javax.servlet.http.HttpServletRequest, java.lang.String)
|
||||
*/
|
||||
public String processAction(HttpServletRequest request, String action) {
|
||||
return action;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.web.servlet.support.RequestDataValueProcessor#
|
||||
* processFormFieldValue(javax.servlet.http.HttpServletRequest,
|
||||
* java.lang.String, java.lang.String, java.lang.String)
|
||||
*/
|
||||
public String processFormFieldValue(HttpServletRequest request,
|
||||
String name, String value, String type) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.web.servlet.support.RequestDataValueProcessor#
|
||||
* getExtraHiddenFields(javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
public Map<String, String> getExtraHiddenFields(HttpServletRequest request) {
|
||||
CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class
|
||||
.getName());
|
||||
if (token == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, String> hiddenFields = new HashMap<String, String>(1);
|
||||
hiddenFields.put(token.getParameterName(), token.getToken());
|
||||
return hiddenFields;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.web.servlet.support.RequestDataValueProcessor#processUrl
|
||||
* (javax.servlet.http.HttpServletRequest, java.lang.String)
|
||||
*/
|
||||
public String processUrl(HttpServletRequest request, String url) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import org.junit.Before;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CsrfAuthenticationStrategyTests {
|
||||
@Mock
|
||||
private CsrfTokenRepository csrfTokenRepository;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private CsrfAuthenticationStrategy strategy;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
strategy = new CsrfAuthenticationStrategy(csrfTokenRepository);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorNullCsrfTokenRepository() {
|
||||
new CsrfAuthenticationStrategy(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logoutRemovesCsrfToken() {
|
||||
strategy.onAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"),request, response);
|
||||
|
||||
verify(csrfTokenRepository).saveToken(null, request, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
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;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Before;
|
||||
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.RequestMatcher;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CsrfFilterTests {
|
||||
|
||||
@Mock
|
||||
private RequestMatcher requestMatcher;
|
||||
@Mock
|
||||
private CsrfTokenRepository tokenRepository;
|
||||
@Mock
|
||||
private FilterChain filterChain;
|
||||
@Mock
|
||||
private AccessDeniedHandler deniedHandler;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
private MockHttpServletResponse response;
|
||||
private CsrfToken token;
|
||||
|
||||
|
||||
private CsrfFilter filter;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
token = new CsrfToken("headerName","paramName", "csrfTokenValue");
|
||||
resetRequestResponse();
|
||||
filter = new CsrfFilter(tokenRepository);
|
||||
filter.setRequireCsrfProtectionMatcher(requestMatcher);
|
||||
filter.setAccessDeniedHandler(deniedHandler);
|
||||
}
|
||||
|
||||
private void resetRequestResponse() {
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorNullRepository() {
|
||||
new CsrfFilter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterAccessDeniedNoTokenPresent() throws ServletException, IOException {
|
||||
when(requestMatcher.matches(request)).thenReturn(true);
|
||||
when(tokenRepository.loadToken(request)).thenReturn(token);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class));
|
||||
verifyZeroInteractions(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");
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class));
|
||||
verifyZeroInteractions(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");
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class));
|
||||
verifyZeroInteractions(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");
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class));
|
||||
verifyZeroInteractions(filterChain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterNotCsrfRequestExistingToken() throws ServletException, IOException {
|
||||
when(requestMatcher.matches(request)).thenReturn(false);
|
||||
when(tokenRepository.loadToken(request)).thenReturn(token);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verifyZeroInteractions(deniedHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterNotCsrfRequestGenerateToken() throws ServletException, IOException {
|
||||
when(requestMatcher.matches(request)).thenReturn(false);
|
||||
when(tokenRepository.generateAndSaveToken(request, response)).thenReturn(token);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verifyZeroInteractions(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());
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verifyZeroInteractions(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());
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verifyZeroInteractions(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());
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verifyZeroInteractions(deniedHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterIsCsrfRequestGenerateToken() throws ServletException, IOException {
|
||||
when(requestMatcher.matches(request)).thenReturn(true);
|
||||
when(tokenRepository.generateAndSaveToken(request, response)).thenReturn(token);
|
||||
request.setParameter(token.getParameterName(), token.getToken());
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verifyZeroInteractions(deniedHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterDefaultRequireCsrfProtectionMatcherAllowedMethods() throws ServletException, IOException {
|
||||
filter = new CsrfFilter(tokenRepository);
|
||||
filter.setAccessDeniedHandler(deniedHandler);
|
||||
|
||||
for(String method : Arrays.asList("GET","TRACE", "OPTIONS", "HEAD")) {
|
||||
resetRequestResponse();
|
||||
when(tokenRepository.loadToken(request)).thenReturn(token);
|
||||
request.setMethod(method);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verifyZeroInteractions(deniedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterDefaultRequireCsrfProtectionMatcherDeniedMethods() throws ServletException, IOException {
|
||||
filter = new CsrfFilter(tokenRepository);
|
||||
filter.setAccessDeniedHandler(deniedHandler);
|
||||
|
||||
for(String method : Arrays.asList("POST","PUT", "PATCH", "DELETE", "INVALID")) {
|
||||
resetRequestResponse();
|
||||
when(tokenRepository.loadToken(request)).thenReturn(token);
|
||||
request.setMethod(method);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class));
|
||||
verifyZeroInteractions(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);
|
||||
|
||||
filter.doFilter(request, response, filterChain);
|
||||
|
||||
assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken());
|
||||
assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token);
|
||||
assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
verifyZeroInteractions(filterChain);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setRequireCsrfProtectionMatcherNull() {
|
||||
filter.setRequireCsrfProtectionMatcher(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setAccessDeniedHandlerNull() {
|
||||
filter.setAccessDeniedHandler(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import org.junit.Before;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CsrfLogoutHandlerTests {
|
||||
@Mock
|
||||
private CsrfTokenRepository csrfTokenRepository;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private CsrfLogoutHandler handler;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
handler = new CsrfLogoutHandler(csrfTokenRepository);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorNullCsrfTokenRepository() {
|
||||
new CsrfLogoutHandler(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logoutRemovesCsrfToken() {
|
||||
handler.logout(request, response, new TestingAuthenticationToken("user", "password", "ROLE_USER"));
|
||||
|
||||
verify(csrfTokenRepository).saveToken(null, request, response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
public class CsrfTokenTests {
|
||||
private final String headerName = "headerName";
|
||||
private final String parameterName = "parameterName";
|
||||
private final String tokenValue = "tokenValue";
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorNullHeaderName() {
|
||||
new CsrfToken(null,parameterName, tokenValue);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorEmptyHeaderName() {
|
||||
new CsrfToken("",parameterName, tokenValue);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorNullParameterName() {
|
||||
new CsrfToken(headerName,null, tokenValue);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorEmptyParameterName() {
|
||||
new CsrfToken(headerName,"", tokenValue);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorNullTokenValue() {
|
||||
new CsrfToken(headerName,parameterName, null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorEmptyTokenValue() {
|
||||
new CsrfToken(headerName,parameterName, "");
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.web.csrf;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
public class HttpSessionCsrfTokenRepositoryTests {
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private CsrfToken token;
|
||||
private HttpSessionCsrfTokenRepository repo;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
repo = new HttpSessionCsrfTokenRepository();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateAndSaveToken() {
|
||||
token = repo.generateAndSaveToken(request, response);
|
||||
|
||||
assertThat(token.getParameterName()).isEqualTo("_csrf");
|
||||
assertThat(token.getToken()).isNotEmpty();
|
||||
|
||||
CsrfToken loadedToken = repo.loadToken(request);
|
||||
|
||||
assertThat(loadedToken).isEqualTo(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateAndSaveTokenCustomParameter() {
|
||||
String paramName = "_csrf";
|
||||
repo.setParameterName(paramName);
|
||||
|
||||
token = repo.generateAndSaveToken(request, response);
|
||||
|
||||
assertThat(token.getParameterName()).isEqualTo(paramName);
|
||||
assertThat(token.getToken()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadTokenNull() {
|
||||
assertThat(repo.loadToken(request)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveToken() {
|
||||
CsrfToken tokenToSave = new CsrfToken("123", "abc", "def");
|
||||
repo.saveToken(tokenToSave, request, response);
|
||||
|
||||
String attrName = request.getSession().getAttributeNames()
|
||||
.nextElement();
|
||||
CsrfToken loadedToken = (CsrfToken) request.getSession().getAttribute(
|
||||
attrName);
|
||||
|
||||
assertThat(loadedToken).isEqualTo(tokenToSave);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveTokenCustomSessionAttribute() {
|
||||
CsrfToken tokenToSave = new CsrfToken("123", "abc", "def");
|
||||
String sessionAttributeName = "custom";
|
||||
repo.setSessionAttributeName(sessionAttributeName);
|
||||
repo.saveToken(tokenToSave, request, response);
|
||||
|
||||
CsrfToken loadedToken = (CsrfToken) request.getSession().getAttribute(
|
||||
sessionAttributeName);
|
||||
|
||||
assertThat(loadedToken).isEqualTo(tokenToSave);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveTokenNullToken() {
|
||||
saveToken();
|
||||
|
||||
repo.saveToken(null, request, response);
|
||||
|
||||
assertThat(request.getSession().getAttributeNames().hasMoreElements())
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setSessionAttributeNameEmpty() {
|
||||
repo.setSessionAttributeName("");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setSessionAttributeNameNull() {
|
||||
repo.setSessionAttributeName(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setParameterNameEmpty() {
|
||||
repo.setParameterName("");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setParameterNameNull() {
|
||||
repo.setParameterName(null);
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.web.servlet.support.csrf;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
public class CsrfRequestDataValueProcessorTests {
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private CsrfRequestDataValueProcessor processor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
processor = new CsrfRequestDataValueProcessor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtraHiddenFieldsNoCsrfToken() {
|
||||
assertThat(processor.getExtraHiddenFields(request)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtraHiddenFieldsHasCsrfToken() {
|
||||
CsrfToken token = new CsrfToken("1", "a", "b");
|
||||
request.setAttribute(CsrfToken.class.getName(), token);
|
||||
Map<String,String> expected = new HashMap<String,String>();
|
||||
expected.put(token.getParameterName(),token.getToken());
|
||||
|
||||
assertThat(processor.getExtraHiddenFields(request)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processAction() {
|
||||
String action = "action";
|
||||
assertThat(processor.processAction(request, action)).isEqualTo(action);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processFormFieldValue() {
|
||||
String value = "action";
|
||||
assertThat(processor.processFormFieldValue(request, "name", value, "hidden")).isEqualTo(value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processUrl() {
|
||||
String url = "url";
|
||||
assertThat(processor.processUrl(request, url)).isEqualTo(url);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user