当前位置 主页 > 服务器问题 > Linux/apache问题 >

    spring security中的csrf防御原理(跨域请求伪造)

    栏目:Linux/apache问题 时间:2019-12-11 21:57

    什么是csrf?

    csrf又称跨域请求伪造,攻击方通过伪造用户请求访问受信任站点。CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI......而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。

    举个例子,用户通过表单发送请求到银行网站,银行网站获取请求参数后对用户账户做出更改。在用户没有退出银行网站情况下,访问了攻击网站,攻击网站中有一段跨域访问的代码,可能自动触发也可能点击提交按钮,访问的url正是银行网站接受表单的url。因为都来自于用户的浏览器端,银行将请求看作是用户发起的,所以对请求进行了处理,造成的结果就是用户的银行账户被攻击网站修改。

    解决方法基本上都是增加攻击网站无法获取到的一些表单信息,比如增加图片验证码,可以杜绝csrf攻击,但是除了登陆注册之外,其他的地方都不适合放验证码,因为降低了网站易用性

    相关介绍:

    http://baike.baidu.com/view/1609487.htm?fr=aladdin

    spring-servlet中配置csrf

     <!-- Spring csrf 拦截器 -->
     <mvc:interceptors>
      <mvc:interceptor>
       <mvc:mapping path="/login" />
       <bean class="com.wangzhixuan.commons.csrf.CsrfInterceptor" />
      </mvc:interceptor>
     </mvc:interceptors>
    
    

    在类中声明Csrf拦截器,用来生成或去除CsrfToken

    import java.io.IOException;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import com.wangzhixuan.commons.scan.ExceptionResolver;
    import com.wangzhixuan.commons.utils.WebUtils;
    
    /**
     * Csrf拦截器,用来生成或去除CsrfToken
     * 
     * @author L.cm
     */
    public class CsrfInterceptor extends HandlerInterceptorAdapter {
     private static final Logger logger = LogManager.getLogger(ExceptionResolver.class);
     
     @Autowired 
     private CsrfTokenRepository csrfTokenRepository;
     
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      HandlerMethod handlerMethod = (HandlerMethod) handler;
      // 非控制器请求直接跳出
      if (!(handler instanceof HandlerMethod)) {
       return true;
      }
      CsrfToken csrfToken = handlerMethod.getMethodAnnotation(CsrfToken.class);
      // 判断是否含有@CsrfToken注解
      if (null == csrfToken) {
       return true;
      }
      // create、remove同时为true时异常
      if (csrfToken.create() && csrfToken.remove()) {
       logger.error("CsrfToken attr create and remove can Not at the same time to true!");
       return renderError(request, response, Boolean.FALSE, "CsrfToken attr create and remove can Not at the same time to true!");
      }
      // 创建
      if (csrfToken.create()) {
       CsrfTokenBean token = csrfTokenRepository.generateToken(request);
       csrfTokenRepository.saveToken(token, request, response);
       // 缓存一个表单页面地址的url
       csrfTokenRepository.cacheUrl(request, response);
       request.setAttribute(token.getParameterName(), token);
       return true;
      }
      // 判断是否ajax请求
      boolean isAjax = WebUtils.isAjax(handlerMethod);
      // 校验,并且清除
      CsrfTokenBean tokenBean = csrfTokenRepository.loadToken(request);
      if (tokenBean == null) {
       return renderError(request, response, isAjax, "CsrfToken is null!");
      }
      String actualToken = request.getHeader(tokenBean.getHeaderName());
      if (actualToken == null) {
       actualToken = request.getParameter(tokenBean.getParameterName());
      }
      if (!tokenBean.getToken().equals(actualToken)) {
       return renderError(request, response, isAjax, "CsrfToken not eq!");
      }
      return true;
     }
     
     private boolean renderError(HttpServletRequest request, HttpServletResponse response, 
       boolean isAjax, String message) throws IOException {
      // 获取缓存的cacheUrl
      String cachedUrl = csrfTokenRepository.getRemoveCacheUrl(request, response);
      // ajax请求直接抛出异常,因为{@link ExceptionResolver}会去处理
      if (isAjax) {
       throw new RuntimeException(message);
      }
      // 非ajax CsrfToken校验异常,先清理token
      csrfTokenRepository.saveToken(null, request, response);
      logger.info("Csrf[redirectUrl]:\t" + cachedUrl);
      response.sendRedirect(cachedUrl);
      return false;
     }
    
     /**
      * 用于清理@CsrfToken保证只能请求成功一次
      */
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
       ModelAndView modelAndView) throws Exception {
      HandlerMethod handlerMethod = (HandlerMethod) handler;
      // 非控制器请求直接跳出
      if (!(handler instanceof HandlerMethod)) {
       return;
      }
      CsrfToken csrfToken = handlerMethod.getMethodAnnotation(CsrfToken.class);
      if (csrfToken == null || !csrfToken.remove()) {
       return;
      }
      csrfTokenRepository.getRemoveCacheUrl(request, response);
      csrfTokenRepository.saveToken(null, request, response);
     }
    
    }