CSRF

CSRF

基于TokenCSRF防范的话,最基础的需要解决Token的生成、存储、查询等问题。针对这些问题,Spring Security定义了CSRF Token仓库接口org.springframework.security.web.csrf.CsrfTokenRepository.java。目前该接口有3个实现类:CookieCsrfTokenRepository、HttpSessionCsrfTokenRepository以及LazyCsrfTokenRepository

CookieCsrfTokenRepositoryToken信息存放于客户端的Cookie中;每次生成Token后将其保存到Cookie中,该TokenCookie一起返回给客户端。客户端请求被CSRF保护的URL时,需要携带Cookie,便于服务端loadToken使用。使用该类型Token仓库时,通常需要允许Javascript脚本从Cookied中获取Token,因此需要将CookiehttpOnly属性值设置为false。如果Javascript不需要从Cookie中获取Token(例如将Token存放于Session,建议将httponly设置为true,以提高安全性。默认存放在Cookie中的Token名为XSRF-TOKEN

服务端在收到请求以后,会从请求中提取Token,并与仓库中的Token校验,以判断该请求是否合法。具体看,Spring Security通过Filter的方式提供了CSRF校验逻辑org.springframework.security.web.csrf.CsrfFilter

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

    // 从Token仓库中加载token。如果不存在则生成并保存之
    CsrfToken csrfToken = this.tokenRepository.loadToken(request);
    final boolean missingToken = csrfToken == null;
    if (missingToken) {
        csrfToken = this.tokenRepository.generateToken(request);
        this.tokenRepository.saveToken(csrfToken, request, response);
    }
    request.setAttribute(CsrfToken.class.getName(), csrfToken);
    request.setAttribute(csrfToken.getParameterName(), csrfToken);

    // 判断是否为需要保护的路径
    if (!this.requireCsrfProtectionMatcher.matches(request)) {
        filterChain.doFilter(request, response);
        return;
    }

    // 从请求头或参数中获取携带的token
    String actualToken = request.getHeader(csrfToken.getHeaderName());
    if (actualToken == null) {
        actualToken = request.getParameter(csrfToken.getParameterName());
    }

    // 比较请求中携带的token和仓库中的token是否一致,若不一致则请求非法
    if (!csrfToken.getToken().equals(actualToken)) {
        ...
    }

    filterChain.doFilter(request, response);
}

Links

上一页