如何配置Spring Security以在multipart / form-data(POST)请求中处理CSRF令牌?

时间:2015-02-06 21:55:30

标签: spring-security

方案

  • 使用HTTP API进行客户端 - 服务器通信的单页(AngluarJS)Web应用程序
  • Spring Security配置为使用CSRF保护(通过XML)
  • CSRF令牌通常在请求标头中发送(工作正常)
  • 应用程序需要支持IE9中的文件上传

问题

通过multipart / form-data POST请求实现文件上传。通常这是使用客户端AJAX完成的,但IE9不支持FileAPI(http://www.w3.org/TR/FileAPI/)。

IE9的解决方法是在隐藏的iframe中创建表单,然后提交表单。 CSRF令牌通过将其添加为表单输入而添加到请求正文中 - 原因是我无法操纵请求标头以在表单提交之前添加CSRF标头。

Spring Security的org.springframework.security.web.csrf.CsrfFilter首先尝试从头文件中获取CSRF令牌,如果没有找到,则尝试从参数中获取它(通过HttpServletRequest.getParameter())

这对于在正文中使用CSRF令牌的多部分请求不起作用 - getParameter()将始终返回null。

顺便说一下,对getParameter()的调用也会将请求InputStream读取到最后,所以我们被迫在它到达CsrfFilter之前将请求换行,以便请求InputStream被'缓存'

我想创建一个调用getPart()的CsrfFilter,但是在使用nice + clean的Spring Security XML命名空间元素时却不能这样做。

原因是在配置中没有地方包含自定义CSRF过滤器 - 并且CsrfConfigurer被硬编码为使用org.springframework.security.web.csrf.CsrfFilter,因此无法注入。

我可以将代码添加到我的请求包装器类的重写的getParameter()方法中,以尝试从多部分请求中解析参数 - 但实际上这是非常棘手的,并且宁愿避免这样的维护成本。

TL; DR

  • 我们无法将CSRF令牌添加到请求标头,需要添加到请求正文
  • 请求是multipart / form-data
  • Spring Security CSRF过滤器不可配置为从多部分请求
  • 解析CSRF令牌

欢迎任何帮助 - 欢迎在客户端或服务器端修复建议!

TIA

2 个答案:

答案 0 :(得分:5)

您应该阅读讨论CSRF and Multipart requests的参考资料中的部分。您有两种选择:

每个都有参考文献中描述的优点/缺点。

最终,如果你想提供一个自定义过滤器,你可以使用 XML element来实现,它只是引用一个实现过滤器的Spring Bean。例如:

<http ...>
   ...
   <custom-filter ref="customCsrfFilter" position="CSRF_FILTER"/>
</http>

答案 1 :(得分:0)

我已经实施了一个似乎现在正在运行的解决方案,但有一些关于它我不喜欢......

  1. 仍在使用Spring Security&#39; <csrf>
  2. 我已在CSRF过滤器之前添加了自定义过滤器:

    <security:custom-filter before="CSRF_FILTER" ref="csrfRequestWrapperFilter" />

  3. 我已将相同的RequestMatcher注入CsrfRequestWrapperFilter,以便仅处理将检查CSRF令牌的请求
  4. 过滤器包裹/装饰HttpServletRequest中的CsrfProtectedHttpServletRequest,然后继续链(下一个是CsrfFilter)
  5. CsrfProtectedHttpServletRequest扩展HttpServletRequestWrapper,并覆盖getParameter(String name)方法。类似的东西:
  6. ...将Request InputStream复制到成员OutputStream ...

        if(this.getContentType() != null && requestBody != null) {
                if(this.getContentType().contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE) && requestBody.contains(parameterName+"="))
                    return getFormEncodedParameter(requestBody, parameterName);
                else if(this.getContentType().contains(MediaType.MULTIPART_FORM_DATA_VALUE)  && requestBody.contains("name="+"\""+ parameterName +"\"")) {
                    return getMultipartParameter(requestBody, parameterName);
                }
            }
        }
    

    这两种方法都做了一些字符串解析...那是我真正不喜欢的部分。它根本不与核心业务逻辑相关,而且我对实现并不是100%有信心,尽管它正在努力获得_csrf&#34;参数&#34;超出多部分请求。