概述

Spring Security Web使用该Filter解决Cross-Site Request Forgery (CSRF)攻击,使用的模式是Synchronizer token pattern (STP)。

STP模式本意是每个请求都生成一个不同的,随机的,不可预测的token用于CSRF保护。这种严格的模式CSRF保护能力很强。但是每请求必验给服务端增加了额外的负担,另外它也要求浏览器必须保持正确的事件顺序,从而会带来一些可用性上的问题(比如用户打开了多个Tab的情况)。所以Spring Security中把这种限制放宽到了每个session使用一个csrf token,并且仅针对会对服务器进行状态更新的HTTP动作:PATCH, POST, PUT,DELETE等。

package org.springframework.security.web.csrf;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;

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.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.UrlUtils;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;


public final class CsrfFilter extends OncePerRequestFilter {
/**
* The default RequestMatcher that indicates if CSRF protection is required or
* not. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other
* requests.
* 用于检测哪些请求需要csrf保护,这里的缺省配置是:GET, HEAD, TRACE, OPTIONS这种只读的
* HTTP动词都被忽略不做csrf保护,而其他PATCH, POST, PUT,DELETE等会修改服务器状态的HTTP
* 动词会受到当前Filter的csrf保护。
*/
public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher();

private final Log logger = LogFactory.getLog(getClass());
private final CsrfTokenRepository tokenRepository;
private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
// 用于CSRF保护验证逻辑失败进行处理
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();

// 构造函数,使用指定的csrf token存储库构造一个CsrfFilter实例
// 缺省情况下,使用Spring Security 的 Springboot web 应用,选择使用的
// csrfTokenRepository是一个做了惰性封装的HttpSessionCsrfTokenRepository实例。
// 也就是说相应的 csrf token保存在http session中。
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.tokenRepository = csrfTokenRepository;
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);

// 从csrf token存储库中获取针对当前请求的csrf token。
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
// 记录针对当前请求是否不存在csrf token
final boolean missingToken = csrfToken == null;
if (missingToken) {
// 如果存储库中尚不存在针对当前请求的csrf token,生成一个,把它关联到
// 当前请求保存到csrf token存储库中
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}

// 将从存储库中获取得到的或者新建并保存到存储库的csrf token保存为请求的两个属性
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);

if (!this.requireCsrfProtectionMatcher.matches(request)) {
// 检测当前请求是否需要csrf保护,如果不需要,放行继续执行filter chain的其他逻辑
filterChain.doFilter(request, response);
return;
}

// 尝试从请求头部或者参数中获取浏览器端传递过来的实际的csrf token。
// 缺省情况下,从头部取出时使用header name: X-CSRF-TOKEN
// 从请求中获取参数时使用的参数名称是 : _csrf
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
// csrf token存储库中取出的token和浏览器端传递过来的token不相等的情况有两种:
// 1. 针对该请求在存储库中并不存在csrf token
// 2. 针对该请求在存储库中的csrf token和请求参数实际携带的不一致
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for "
+ UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
// 1. 针对该请求在存储库中并不存在csrf token , 处理方案:
// 抛出异常 MissingCsrfTokenException
this.accessDeniedHandler.handle(request, response,
new MissingCsrfTokenException(actualToken));
}
else {
// 2. 针对该请求在存储库中的csrf token和请求参数实际携带的不一致,处理方案:
// 抛出异常 InvalidCsrfTokenException
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}

// 当前请求需要经该Filter的csrf验证逻辑并且通过了csrf验证,放行,继续执行filter chain
// 其他部分逻辑
filterChain.doFilter(request, response);
}

/**
* Specifies a RequestMatcher that is used to determine if CSRF protection
* should be applied. If the RequestMatcher returns true for a given request,
* then CSRF protection is applied.
*
* 指定一个RequestMatcher用来检测一个请求是否需要应用csrf保护验证逻辑。
*
* The default is to apply CSRF protection for any HTTP method other than GET, HEAD,
* TRACE, OPTIONS.
* 缺省行为是针对GET, HEAD,TRACE, OPTIONS这种只读性的HTTP请求不做csrf保护验证,验证其他
* 那些会更新服务器状态的HTTP请求,比如PATCH, POST, PUT,DELETE等。
*
*
* @param requireCsrfProtectionMatcher the 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 AccessDeniedHandler that should be used when CSRF protection
* fails.
* 指定一个AccessDeniedHandler用于CSRF保护验证逻辑失败进行处理。
*
* The default is to use AccessDeniedHandlerImpl with no arguments.
* 缺省行为是使用一个不但参数的AccessDeniedHandlerImpl实例。
*
* @param accessDeniedHandler the AccessDeniedHandler to use
*/
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
this.accessDeniedHandler = accessDeniedHandler;
}
// 用于检测哪些HTTP请求需要应用csrf保护的RequestMatcher,
// 缺省行为是针对GET, HEAD,TRACE, OPTIONS这种只读性的HTTP请求不做csrf保护,
// 其他那些会更新服务器状态的HTTP请求,比如PATCH, POST, PUT,DELETE等需要csrf保护。
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
private final HashSet<String> allowedMethods = new HashSet<>(
Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

@Override
public boolean matches(HttpServletRequest request) {
return !this.allowedMethods.contains(request.getMethod());
}
}
}

其他过滤器总和

​​SpringSecurity过滤器清单​​