package com.zblog.web.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import com.zblog.core.Constants;
import com.zblog.core.util.CookieUtil;
import com.zblog.core.util.ServletUtils;
import com.zblog.core.util.StringUtils;

/**
 * 处理csrf攻击,Stateless CSRF方案,由客户端产生crsf的cookie和请求参数
 * 
 * @author zhou
 * 
 */
public class StatelessCsrfFilter extends OncePerRequestFilter{
  private List<String> excludes = new ArrayList<>();
  static List<String> METHODS = Arrays.asList("POST", "DELETE", "PUT", "PATCH");
  private PathMatcher matcher = new AntPathMatcher();

  @Override
  protected void initFilterBean() throws ServletException{
    FilterConfig config = getFilterConfig();
    String paths = config.getInitParameter("exclude");
    if(!StringUtils.isBlank(paths)){
      excludes.addAll(Arrays.asList(paths.split(",")));
    }
  }

  private boolean isAjaxVerificationToken(HttpServletRequest request, String csrfToken){
    String headToken = request.getHeader(Constants.CSRF_TOKEN);

    return headToken != null && headToken.equals(csrfToken);
  }

  /**
   * 校验非ajax提交
   * <p>
   * 由于有可能为multipart提交所以须在该filter前做parseMultipart,否则request.getParameter会获取不到值
   * </p>
   * 
   * @param request
   * @param csrfToken
   * @return
   */
  private boolean isVerificationToken(HttpServletRequest request, String csrfToken){
    String paramToken = request.getParameter(Constants.CSRF_TOKEN);

    if(StringUtils.isBlank(paramToken))
      return false;

    return paramToken.equals(csrfToken);
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException{
    String url = request.getRequestURI();
    for(String match : excludes){
      if(matcher.match(match, url)){
        filterChain.doFilter(request, response);
        return;
      }
    }

    if(METHODS.contains(request.getMethod())){
      boolean ajax = ServletUtils.isAjax(request);
      CookieUtil cookieUtil = new CookieUtil(request, response);
      String csrfToken = cookieUtil.getCookie(Constants.COOKIE_CSRF_TOKEN, false);
      if(ajax && !isAjaxVerificationToken(request, csrfToken)){
        response.setContentType("application/json");
        response.setCharacterEncoding(Constants.ENCODING_UTF_8.name());
        response.getWriter().write("{'status':'403','success':false,'msg':'非法请求,请刷新重试'}");
        return;
      }else if(!ajax && !isVerificationToken(request, csrfToken)){
        // if(response.isCommitted())
        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
        return;
      }
    }

    filterChain.doFilter(request, response);
  }

}

@@ -3015,24 +3015,108 @@
 mmitted())%0D%0A
+        //%E8%BF%99%E4%B8%AA%E9%94%99%E8%AF%AF%E6%B2%A1%E6%9C%89%E7%BB%99%E5%87%BA%E6%98%8E%E7%A1%AE%E9%94%99%E8%AF%AF%E4%BF%A1%E6%81%AF%EF%BC%8C%E5%AF%BC%E8%87%B4%E5%BC%80%E5%8F%91%E8%80%85%E4%B8%8D%E4%BE%BF%E8%BF%BD%E8%B8%AA%E9%97%AE%E9%A2%98%EF%BC%8C%E4%B8%80%E7%9B%B4%E4%BB%A5%E4%B8%BA%E6%98%AFspring %E5%8F%82%E6%95%B0%E8%A7%A3%E6%9E%90%E9%97%AE%E9%A2%98%EF%BC%8C%E5%BD%93%E7%84%B6tomcat%E7%AD%89%E5%AE%B9%E5%99%A8%E6%B2%A1%E6%9C%89%E8%AE%BE%E7%BD%AE%E9%A1%B9%E7%9B%AE%E5%90%8D%E8%AE%BF%E9%97%AE%E6%98%AF%E6%B2%A1%E6%9C%89%E9%97%AE%E9%A2%98%E7%9A%84%0D%0A
         resp
@@ -3164,16 +3164,29 @@
 _REQUEST
+,%22%E9%9D%9E%E6%B3%95%E8%AF%B7%E6%B1%82%EF%BC%8C%E8%AF%B7%E5%88%B7%E6%96%B0%E9%87%8D%E8%AF%95%22
 );%0D%0A    

Update StatelessCsrfFilter.java