springboot + security 自定义csrf校验结果

查看csrfFilter源码,会先去HttpSessionCsrfTokenRepository.loadToken加载CsrfToken ,其实就是从session中获取。

public CsrfToken loadToken(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return null;
        }
        return (CsrfToken) session.getAttribute(this.sessionAttributeName);
    }

如果不存在,会创建一个CsrfToken 并放入session

public void saveToken(CsrfToken token, HttpServletRequest request,
            HttpServletResponse response) {
        if (token == null) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                session.removeAttribute(this.sessionAttributeName);
            }
        }
        else {
            HttpSession session = request.getSession();
            session.setAttribute(this.sessionAttributeName, token);
        }
    }

之后会从request中取token,与从session中取出的token对比,所以这里开启csrf认证后,这四种请求是不验证csrf的”GET”, “HEAD”, “TRACE”, “OPTIONS”,常用的POST请求,每次都要么在请求头上加上X-CSRF-TOKEN:csrf值,或者在POST中加入参数_csrf:csrf值,这样才能取出request中的csrf和session中的对比。

        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) {
                this.accessDeniedHandler.handle(request, response,
                        new MissingCsrfTokenException(actualToken));
            }else {
                this.accessDeniedHandler.handle(request, response,
                        new InvalidCsrfTokenException(csrfToken, actualToken));
            }
            return;
        }

如果request中取出的csrf和session中取出的不相等,会进入accessDeniedHandler.handle,这个accessDeniedHandler是AccessDeniedHandlerImpl,里面方法如下

public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    if(!response.isCommitted()) {
      if(this.errorPage != null) {
        request.setAttribute("SPRING_SECURITY_403_EXCEPTION", accessDeniedException);
        response.setStatus(403);
        RequestDispatcher dispatcher = request.getRequestDispatcher(this.errorPage);
        dispatcher.forward(request, response);
      } else {
        response.sendError(403, accessDeniedException.getMessage());
      }
    }

如果设置了errorPage会服务器内部转发到该路径,之后还是会经过security的各个filter进行登陆操作,最终登陆成功。这不是我要的,,所以需要自定义一个accessDeniedHandler。代码如下,如果不是登陆请求的 csrf不匹配,都退出当前用户,登陆用户不做csrf校验。

@Component
public class CsrfAccessDeniedHandler implements AccessDeniedHandler {
    private SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
  @Autowired
  private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
  @Override
  public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
      Authentication auth = SecurityContextHolder.getContext().getAuthentication();
      logoutHandler.logout(request, response, auth);
      ajaxLogoutSuccessHandler.onLogoutSuccess(request, response, auth);
  }

然后在securityConfig里配置

@Override
  protected void configure(HttpSecurity http) throws Exception {
    // 允许iframe
    http.headers().frameOptions().sameOrigin();
    //登陆页面不做csrf校验
    http.csrf().ignoringAntMatchers("/login");

    //异常处理
    http.exceptionHandling().
        accessDeniedHandler(csrfAccessDeniedHandler)
}

这里有一篇讨论csrf的文章https://segmentfault.com/q/1010000000713614

你可能感兴趣的:(springboot,security)