再谈Spring MVC中对于CSRF攻击的防御

在Spring MVC应用中实施CSRF防御, 一般会采用 EYAL LUPU 的方案,该方案的基本思路是在生成表单时在其中插入一个随机数作为签名,在表单提交后对其中的签名进行 验证 ,根据 验证 的结果区分该表单是否是经由应用签署的合法表单。如果签名不正确或不存在签名,则说明请求可能已被劫持。


EYAL LUPU方案的巧妙之处在于,通过使用HandlerInterceptorAdapter和Spring3.1中新引入的ReuqestDataValueProcessor这一对组合,使得签名和验证的过程无缝地集成到现有应用中。Controller或Model层的对象可以仍然只关心自己的业务逻辑,完全不必考虑CSRF过程的存在;唯一的限制是在View层,必须使用Spring的<form>标签来渲染表单。

对请求的验证在拦截器的preHandle方法中,当验证通过后,方法返回true,请求将沿着处理链继续传递;但如果验证失败,方法返回false,请求将被截停,并发送一个HTTP 400的状态代码作为响应。

如果没有在web.xml中使用error-page为应用自定义错误页,400状态码将直接被发送给客户端浏览器,浏览器会显示一个缺省的错误页面,到目前为之一切都很完美。

但如果使用error-page指定了错误页面,问题来了,Servlet容器会首先根据响应状态码把原始的请求转发(forward)给具体的错误页面,然后该错误页面才被发送到客户端浏览器。需要注意的是,由于我们使用了拦截器,这次forward请求会再次被拦截,preHandle方法中的验证过程也会再次被触发,如果不加处理的话,会再次验证失败,因为具体的请求仍然是最初的请求。

解决的方案如下:
1. 在Spring的dispatcher-servlet中加入mvc:default-servlet-handler。
这样做的目的是确保未被Dispatcher实际处理的请求,例如向错误页面的转发或对静态资源的访问等,会被传回给Servlet容器。

2. 修改preHandle方法,在方法的开始检测方法的第三个参数的类型。
该参数代表处理链中即将处理请求的下一个对象,如果该参数是DefaultServletHttpRequestHandler类的实例,则说明请求即将交由Servlet容器处理,对于此类请求直接放行即可。

3. 最后,确保所有errro-page中声明的URL不会被任何Controller处理,也不会被ResourceHttpRequestHandler处理。
被Controller处理过就没机会沿处理链将请求交给Servlet容器了。另外,应用 一般 会在dispatcher中用mvc:resources声明静态资源,如果把错误页面也包含进来,请求会在被交给DefaultServletHttpRequestHanlder前先被 ResourceHttpRequestHandler匹配到,后者为了优化对静态资源的处理,默认是只支持GET和HEAD方法的,而此时的请求是从表单POST来的,因此会引发一个Request method 'POST' not supported错误,而浏览器端只能得到一个405响应,无法显示预期的错误页页面。

调整后的preHandle方法类似下面这样:
public boolean preHandle(HttpServletRequest request,
               HttpServletResponse response, Object handler) throws Exception {

          if (handler instanceof DefaultServletHttpRequestHandler) {
               return true;
          }
          
          if (!request.getMethod().equalsIgnoreCase(
                    WebContentGenerator.METHOD_POST)) {
               // 忽略非POST请求
               return true;
          } else {
               // 验证CSRF签名
               //if (passed)
               //     return true;
               //else {
               //     response.sendError(HttpServletResponse.SC_BAD_REQUEST,
               //               "Bad or missing CSRF value");
               //     return false;
               //}
          }
} 



原文地址:https://www.cnblogs.com/javawebsoa/p/3087634.html