SpringSecurity的防Csrf攻击实现代码解析

2020-03-03 16:02:03王旭

下面是csrfFilter的过滤过程

@Override
 protected void doFilterInternal(HttpServletRequest request,
   HttpServletResponse response, FilterChain filterChain)
     throws ServletException, IOException {
  request.setAttribute(HttpServletResponse.class.getName(), response);
    
    //获取到cookie中的csrf Token(CookieTokenRepository)或者从session中获取(HttpSessionCsrfTokenRepository)
  CsrfToken csrfToken = this.tokenRepository.loadToken(request);
  final boolean missingToken = csrfToken == null;
    //加载不到,则证明请求是首次发起的,应该生成并保存一个新的 CsrfToken 值
  if (missingToken) {
   csrfToken = this.tokenRepository.generateToken(request);
   this.tokenRepository.saveToken(csrfToken, request, response);
  }
  request.setAttribute(CsrfToken.class.getName(), csrfToken);
  request.setAttribute(csrfToken.getParameterName(), csrfToken);

    //排除部分不需要验证CSRF攻击的请求方法(默认忽略了GET、HEAD、TRACE和OPTIONS)
  if (!this.requireCsrfProtectionMatcher.matches(request)) {
   filterChain.doFilter(request, response);
   return;
  }

    //实际的token从header或者parameter中获取
  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;
  }

  filterChain.doFilter(request, response);
 }

用户想要坚持CSRF Token在cookie中。 默认情况下CookieCsrfTokenRepository将编写一个名为 XSRF-TOKEN的cookie和从头部命名 X-XSRF-TOKEN中读取或HTTP参数 _csrf。

//代码如下:
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

我们在日常使用中,可以采用header或者param的方式添加csrf_token,下面示范从cookie中获取token

<form action="/executeLogin" method="post">
<p>Sign in to continue</p>
<div class="lowin-group">
 <label>用户名 <a href="#" rel="external nofollow" rel="external nofollow" class="login-back-link">Sign in?</a></label>
 <input type="text" name="username" class="lowin-input">
</div>
<div class="lowin-group password-group">
 <label>密码 <a href="#" rel="external nofollow" rel="external nofollow" class="forgot-link">Forgot Password?</a></label>
 <input type="password" name="password" class="lowin-input">
</div>
<div class="lowin-group">
 <label>验证码</label>
 <input type="text" name="kaptcha" class="lowin-input">
 <img src="/kaptcha.jpg" alt="kaptcha" height="50px" width="150px" style="margin-left: 20px">
</div>
<div class="lowin-group">
 <label>记住我</label>
 <input name="remember-me" type="checkbox" value="true" />
</div>
<input type="hidden" name="_csrf">
<input class="lowin-btn login-btn" type="submit">
</form>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> 
<script>
 $(function () {
  var aCookie = document.cookie.split("; ");
  console.log(aCookie);
  for (var i=0; i < aCookie.length; i++)
  {
   var aCrumb = aCookie[i].split("=");
   if ("XSRF-TOKEN" == aCrumb[0])
    $("input[name='_csrf']").val(aCrumb[1]);
  }
 });
</script>