美文网首页
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