【问题标题】:Spring Security 3.2 CSRF support for multipart requestsSpring Security 3.2 CSRF 对多部分请求的支持
【发布时间】:2014-02-19 07:11:37
【问题描述】:

几年来,我们一直在我们的应用程序中使用 Spring Security。上周我们将 Spring Security 从版本 3.1.4 升级到了 3.2.0。升级很顺利,升级后我们没有发现任何错误。

在查看 Spring Security 3.2.0 文档时,我们发现了围绕 CSRF 保护和安全标头的新增功能。我们按照 Spring Security 3.2.0 文档中的说明为我们受保护的资源启用 CSRF 保护。它适用于常规表单,但不适用于我们应用程序中的多部分表单。在提交表单时,CsrfFilter 抛出拒绝访问错误,理由是请求中没有 CSRF 令牌(通过调试日志确定)。我们已经尝试使用Spring Security documentation 中建议的第一个选项来使 CSRF 保护与多部分表单一起工作。我们不想使用第二个建议的选项,因为它会通过 URL 泄露 CSRF 令牌并带来安全风险。

我们基于文档配置的相关部分在 Github 上以Gist 的形式提供。我们使用的是 Spring 4.0.0 版。

请注意,我们已经尝试了以下变体但没有成功:

  1. 未在web.xml 中声明MultipartFilter
  2. 未在web.xml 中设置MultipartFilter 的解析器bean 名称。
  3. webContext.xml 中使用默认解析器bean 名称filterMultipartResolver

更新:我已经确认记录的行为即使使用单页示例应用程序也不起作用。任何人都可以确认记录的行为按预期工作吗?是否有可以使用的示例工作应用程序?

【问题讨论】:

    标签: spring spring-mvc spring-security csrf


    【解决方案1】:

    这部分:

    <filter-mapping>
        <filter-name>multipartFilter</filter-name>
        <servlet-name>/*</servlet-name>
    </filter-mapping>
    

    应该是:

    <filter-mapping>
        <filter-name>multipartFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    这是 Spring Security 3.2.0 文档中的错误。错误has been reported,将在即将发布的版本中修复。

    【讨论】:

    • 感谢@holmis83 的建议。这确实是我们的设置不起作用的原因之一。但是,配置还有其他问题,我在 Spring Security 团队的帮助下得以解决。请查看我对此问题的回答,了解使我们的应用程序正常运行的完整配置。
    【解决方案2】:

    在 Spring Security 团队的帮助下,我能够解决这个问题。我已更新 Gist 以反映工作配置。为了让一切按预期工作,我必须按照下面给出的步骤进行操作。


    1.常用步骤

    按照the answer by @holmis83 中的说明将MultipartFilter 添加到web.xml,确保在Spring Security 配置之前添加:

    <filter>
        <display-name>springMultipartFilter</display-name>
        <filter-name>springMultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springMultipartFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter>
        <display-name>springSecurityFilterChain</display-name>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>ERROR</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    

    2.1.使用 Apache Commons 多部分解析器

    确保在根 Spring 应用程序上下文中存在一个名为 filterMultipartResolver 的 Apache Commons Multipart Resolver bean。我将再次强调这一点,确保 Multipart Resolver 在根 Spring Context 中声明(通常称为 applicationContext.xml)。例如,

    web.xml

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:springWebMultipartContext.xml
        </param-value>
    </context-param>
    

    springWebMultipartContext.xml

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="filterMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <property name="maxUploadSize" value="100000000" />
        </bean>
    </beans>
    

    确保 bean 被称为 filterMultipartResolver,因为在 web.xml 中配置的 MultipartFilter 不会拾取任何其他 bean 名称。我的初始配置不起作用,因为这个 bean 被命名为 multipartResolver。我什至尝试使用 web.xml init-param 将 bean 名称传递给 MultipartFilter,但这也不起作用。

    2.2.使用 Tomcat Multipart 支持

    Tomcat 7.0+ 具有内置的多部分支持,但必须明确启用。如下更改全局 Tomcat context.xml 文件或在您的 WAR 文件中包含一个本地 context.xml 文件,以使此支持能够正常工作,而无需对您的应用程序进行任何其他更改。

    <Context allowCasualMultipartParsing="true">
        ...
    </Context>
    

    在使用 Apache Commons Multipart Resolver 进行这些更改后,我们的应用程序目前在 Tomcat、Jetty 和 Weblogic 上运行。

    【讨论】:

    • 赞成“确保 bean 被称为 filterMultipartResolver,因为在 web.xml 中配置的 MultipartFilter 没有拾取任何其他 bean 名称”。也适用于注解配置,bean方法名应该是filterMultipartResolver。示例:@Bean public MultipartResolver filterMultipartResolver() {...} 在执行此操作之前,我收到了错误:** java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided**。
    • 谢谢,文档并没有帮助我解决问题。我必须将 Apache Commons File Upload 添加到我的 pom.xml 以使一切正常。
    • 每次我设置这个过滤器时,我总是得到一个空的上传文件,知道为什么吗?
    • 我想更清楚一点,名为 filterMultipartResolver 的多部分解析器 bean 必须保留在 ROOT Spring 应用程序上下文中,而不是在 ( non-CSRF) 没有过滤器的设置,必须命名为 multipartResolver 并且可以保留在 WEB Spring 应用程序上下文
    • 我第二次想到@jpganz18,因为我上传的文件太空,而且我的多部分过滤器无法找到请求中的部分。对此有什么想法吗??
    【解决方案3】:

    在稍微解决了这个问题之后,我找到了一个更简单的解决方案,只使用 Spring Security 中定义的请求标头,而不是尝试将 CSRF 令牌嵌入作为多部分内容的一部分。

    这是一种简单的方法,我使用 AJAX 库在我的 jsp 中设置标题以进行文件上传:

    var uploader = new AjaxUpload({
            url: '/file/upload',
            name: 'uploadfile',
            multipart: true,
            customHeaders: { '${_csrf.headerName}': '${_csrf.token}' },
            ...
            onComplete: function(filename, response) {
                ...
            },
            onError: function( filename, type, status, response ) {
                ...
            }
    });
    

    这又发送了带有标头的多部分请求:

    X-CSRF-TOKEN: abcdef01-2345-6789-abcd-ef0123456789
    

    他们建议在标头中嵌入 &lt;meta /&gt; 标记也可以通过在提交时暂停请求、通过 javascript 添加标头然后完成提交来正常工作:

    <html>
    <head>
        <meta name="_csrf" content="${_csrf.token}"/>
        <!-- default header name is X-CSRF-TOKEN -->
        <meta name="_csrf_header" content="${_csrf.headerName}"/>
        <!-- ... -->
    </head>
    <body>
        <!-- ... -->
        <script>
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");
            // Do whatever with values
        </script>
    </body>
    </html>
    

    更多信息:Spring Security - CSRF for AJAX and JSON Requests

    【讨论】:

    【解决方案4】:

    查找大多数答案是几年前服务器回答的。

    如果你需要

    使用 RestTemplate 传递 CSRF 令牌

    这个博客很有启发https://cloudnative.tips/passing-csrf-tokens-with-resttemplate-736b336a6cf6

    在 Spring Security 5.0.7.RELEASE 中

    https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-multipart

    使用 CSRF 保护有两种选择 多部分/表单数据。每个选项都有其权衡。

    -在 Spring Security 之前放置 MultipartFilter
    - 包含 CSRF 令牌 行动

    简而言之,第一种选择更安全,后者更容易。

    在 Spring Security 过滤器之前指定 MultipartFilter 表示没有调用 MultipartFilter 的权限 这意味着任何人都可以在您的服务器上放置临时文件。然而, 只有授权用户才能提交已处理的文件 通过您的应用程序。一般来说,这是推荐的方法 因为临时文件上传应该对 大多数服务器。

    确保在 Spring Security 之前指定 MultipartFilter 使用java配置过滤,用户可以覆盖 beforeSpringSecurityFilterChain 如下图:

    public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
    
      @Override
      protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
          insertFilters(servletContext, new MultipartFilter());
      }
    }
    

    确保在 Spring Security 之前指定 MultipartFilter 过滤器与 XML 配置,用户可以确保 MultipartFilter 的元素放置在 web.xml中的springSecurityFilterChain如下图:

    <filter>
      <filter-name>MultipartFilter</filter-name>
      <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
    </filter>
    <filter>
      <filter-name>springSecurityFilterChain</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>MultipartFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    另一种选择

    如果不允许未经授权的用户上传临时文件 可以接受,另一种方法是将 MultipartFilter 放在 Spring Security 过滤器并将 CSRF 作为查询参数包含在 表单的动作属性。下面是一个带有jsp的例子

    <form action="./upload?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">
    

    这种方法的缺点是查询参数可以 泄露。更一般地说,最好的做法是放置 正文或标头中的敏感数据,以确保不会泄露。

    【讨论】:

    • 一开始我是把CSRF参数作为输入标签放在form标签里,结果不行。然后我将 CSRF 参数作为查询字符串移动到表单标签的 action 属性中,它可以工作。感谢详细介绍
    • @ParagFlume 很高兴听到这个答案很有帮助。如果不麻烦,您可以投票支持它。
    猜你喜欢
    • 2014-09-30
    • 2013-02-10
    • 1970-01-01
    • 1970-01-01
    • 2013-04-20
    • 1970-01-01
    • 2017-07-01
    相关资源
    最近更新 更多