美文网首页
Spring Security解析六:CsrfConfigure

Spring Security解析六:CsrfConfigure

作者: 一根线条 | 来源:发表于2020-04-11 15:00 被阅读0次

    HttpSecurity的默认配置如下:

    http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                    sharedObjects);
    http.
      .csrf().and()
      .addFilter(new WebAsyncManagerIntegrationFilter())
      .exceptionHandling().and()
      .headers().and()
      .sessionManagement().and()
      .securityContext().and()
      .requestCache().and()
      .anonymous().and()
      .servletApi().and()
      .apply(new DefaultLoginPageConfigurer<>()).and()
      .logout();
    

    从上面可知,第一个安全配置是针对CSRF(跨站点请求伪造:Cross-Site Request Forgery)的。一般来讲,为了防御CSRF攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证;在 HTTP 头中自定义属性并验证。

    HttpSecurity的csrf() 方法定义如下:

    public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
        ApplicationContext context = getContext();
        return getOrApply(new CsrfConfigurer<>(context));
    }
    

    可见其配置类为CsrfConfigurer,其部分定义如下:

    public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
            extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
        //Csrf保护使用Token的存储库,默认使用Session保存
        private CsrfTokenRepository csrfTokenRepository = new LazyCsrfTokenRepository(
                new HttpSessionCsrfTokenRepository());
        //需要Csrf保护的请求匹配规则
        private RequestMatcher requireCsrfProtectionMatcher = CsrfFilter.DEFAULT_CSRF_MATCHER;
        //不需要Csrf保护的请求匹配规则
        private List<RequestMatcher> ignoredCsrfProtectionMatchers = new ArrayList<>();
        //会话验证策略
        private SessionAuthenticationStrategy sessionAuthenticationStrategy;
        private final ApplicationContext context;
    
        public CsrfConfigurer(ApplicationContext context) {
            this.context = context;
        }
    
        /*
        * @param http  HttpSecurity
        */
        @Override
        public void configure(H http) {
            //用于Csrf保护的拦截器(内部的执行过程便是Csrf保护的过程)
            CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
            //默认为DefaultRequiresCsrfMatcher
            RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
            if (requireCsrfProtectionMatcher != null) {
                filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
            }
            //访问拒绝处理器。如果缺失csrf的token时会被执行
            AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
            if (accessDeniedHandler != null) {
                filter.setAccessDeniedHandler(accessDeniedHandler);
            }
            //注销时候的配置;获取注销的配置类,并添加回调,目的是注销时清除csrf的token
            LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
            if (logoutConfigurer != null) {
                logoutConfigurer
                        .addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
            }
            //访问会话管理配置,添加基于Csrf的会话认证策略
            //如果有csrf的token则先清除再重建且保存
            SessionManagementConfigurer<H> sessionConfigurer = http
                    .getConfigurer(SessionManagementConfigurer.class);
            if (sessionConfigurer != null) {
                sessionConfigurer.addSessionAuthenticationStrategy(
                        getSessionAuthenticationStrategy());
            }
            //后置处理器
            //CompositeObjectPostProcessor、AutowireBeanFactoryObjectPostProcessor
            filter = postProcess(filter);
            http.addFilter(filter);
        }
    
        private AccessDeniedHandler createAccessDeniedHandler(H http) {
            //取得SessionManagementConfigurer里面配置的InvalidSessionStrategy
            //默认处理器,首先获取ExceptionHandlingConfigurer中配置的AccessDeniedHandler,
            //如果没有则使用默认的AccessDeniedHandlerImpl;默认响应403
            InvalidSessionStrategy invalidSessionStrategy = getInvalidSessionStrategy(http);
            AccessDeniedHandler defaultAccessDeniedHandler = getDefaultAccessDeniedHandler(
                    http);
            if (invalidSessionStrategy == null) {
                return defaultAccessDeniedHandler;
            }
    
            //使用invalidSessionStrategy进行处理,例如对请求重定向等
            InvalidSessionAccessDeniedHandler invalidSessionDeniedHandler = new InvalidSessionAccessDeniedHandler(
                    invalidSessionStrategy);
    
            LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler> handlers = new LinkedHashMap<>();
            //指定MissingCsrfTokenException类型的异常可被invalidSessionDeniedHandler处理
            handlers.put(MissingCsrfTokenException.class, invalidSessionDeniedHandler);
            //该处理的逻辑是,如果处理的异常为MissingCsrfTokenException则使用invalidSessionDeniedHandler处理,
            //否则使用默认的defaultAccessDeniedHandler处理
            return new DelegatingAccessDeniedHandler(handlers, defaultAccessDeniedHandler);
        }
    }
    

    CsrfFilter进行请求拦截的处理过程

    public final class CsrfFilter extends OncePerRequestFilter {
        /**
         * The default {@link RequestMatcher} that indicates if CSRF protection is required or
         * not. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other
         * requests.
         */
        public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher();
    
        private final CsrfTokenRepository tokenRepository;
        private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
        private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
    
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                HttpServletResponse response, FilterChain filterChain)
                        throws ServletException, IOException {
            request.setAttribute(HttpServletResponse.class.getName(), response);
    
            //得到保存的token
            CsrfToken csrfToken = this.tokenRepository.loadToken(request);
            final boolean missingToken = csrfToken == null;
            if (missingToken) {
                //创建新的csrf的token
                csrfToken = this.tokenRepository.generateToken(request);
                //将csrf的token进行保存
                this.tokenRepository.saveToken(csrfToken, request, response);
            }
            request.setAttribute(CsrfToken.class.getName(), csrfToken);
            request.setAttribute(csrfToken.getParameterName(), csrfToken);
    
            //判断是否是需要Csrf包含的请求,如果不是则执行后续的过滤器链
            if (!this.requireCsrfProtectionMatcher.matches(request)) {
                filterChain.doFilter(request, response);
                return;
            }
    
            //从Http的请求头或请求参数中得到csrf的token
            String actualToken = request.getHeader(csrfToken.getHeaderName());
            if (actualToken == null) {
                actualToken = request.getParameter(csrfToken.getParameterName());
            }
            if (!csrfToken.getToken().equals(actualToken)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Invalid CSRF token found for "
                            + UrlUtils.buildFullRequestUrl(request));
                }
                if (missingToken) {
                    //此处调用了处理器,并传递了MissingCsrfTokenException异常
                    this.accessDeniedHandler.handle(request, response,
                            new MissingCsrfTokenException(actualToken));
                } else {
                    //此处调用了处理器,并传递了InvalidCsrfTokenException异常
                    this.accessDeniedHandler.handle(request, response,
                            new InvalidCsrfTokenException(csrfToken, actualToken));
                }
                return;
            }
    
            filterChain.doFilter(request, response);
        }
    }
    

    以上便是Spring Security中进行Csrf保护的大致处理过程,其思路就是生成一个csrf的token并保存到session中,然后从每次的请求中得到携带的csrf的token并与session中保存的做比对,如果不一致则验证不通过。

    这个csrf的token其实就是一个uuid字符串,同时,默认只处理除了GET, HEAD, TRACE, OPTIONS方法外的其他请求。

    相关文章

      网友评论

          本文标题:Spring Security解析六:CsrfConfigure

          本文链接:https://www.haomeiwen.com/subject/druhuhtx.html