Spring Security Web 5.1.2 源码解析 — CsrfFilter

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

概述

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());
            }
        }
    }

参考文章

Spring Security Web 5.1.2 源码解析 – 安全相关Filter清单
Cross-Site Request Forgery (CSRF)
Synchronizer token pattern (STP)
10.6.2 Synchronizer Token Pattern


来源:http://ddrv.cn/a/88268

赞(1) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Security Web 5.1.2 源码解析 — CsrfFilter

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏