SpringSecurity源码之CsrfFilter
一、是什么
org.springframework.security.web.csrf.CsrfFiltercsrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的csrf的token信息,如果不包含,则报错。起到防止csrf攻击的效果。
二、源码介绍
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
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 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 {
// 默认允许的请求方法
public static final RequestMatcher DEFAULT_CSRF_MATCHER = new CsrfFilter.DefaultRequiresCsrfMatcher();
private final Log logger = LogFactory.getLog(this.getClass());
// CsrfToken存储库,保存token信息
private final CsrfTokenRepository tokenRepository;
// 保存当前使用的请求方法匹配器,默认是DEFAULT_CSRF_MATCHER,可以通过setRequireCsrfProtectionMatcher自定义
private RequestMatcher requireCsrfProtectionMatcher;
// 用于CSRF保护验证逻辑失败进行处理
private AccessDeniedHandler accessDeniedHandler;
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
this.requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
this.accessDeniedHandler = new AccessDeniedHandlerImpl();
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.tokenRepository = csrfTokenRepository;
}
// 父类的doFilter方法会调用此方法,可以看出SpringSecurity的csrf机制把请求方式分成两类来处理:默认方法(或者自定义的匹配方法)就放过,否则就过逻辑
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);
boolean missingToken = csrfToken == null;
// 如果没有token,就生成一个token,并保存到csrf token存储库中
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
// 存储库中的token信息和请求有关联
this.tokenRepository.saveToken(csrfToken, request, response);
}
//保存token信息到请求域
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
// 如果是默认方法(或者自定义的匹配方法),就放过
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
} else {
// 尝试从请求头部或者参数中获取浏览器端传递过来的实际的csrf token。
// 缺省情况下,从头部取出时使用header name: X-CSRF-TOKEN
// 从请求中获取参数时使用的参数名称是 : _csrf
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
// 不匹配或者没有token,就报异常
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
this.accessDeniedHandler.handle(request, response, new MissingCsrfTokenException(actualToken));
} else {
this.accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken));
}
} else {
filterChain.doFilter(request, response);
}
}
}
public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) {
Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null");
this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
}
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
this.accessDeniedHandler = accessDeniedHandler;
}
// Csrf默认的请求匹配器,定义默认的请求方法
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
private final HashSet<String> allowedMethods;
private DefaultRequiresCsrfMatcher() {
this.allowedMethods = new HashSet(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
}
public boolean matches(HttpServletRequest request) {
//返回true代表不是默认的方法,false代表是默认的方法
return !this.allowedMethods.contains(request.getMethod());
}
}
}