Webflux multipart / form-data,启用csrf,带有和不带有文件上传功能的CSRF令牌均无效

时间:2018-08-29 10:03:00

标签: spring spring-boot spring-security multipartform-data spring-webflux

在禁用csrf的情况下,我可以上传文件,但是我需要将其启用。 仅当表单enctype为multipart / form-data(即带有403的“无效CSRF令牌”)时,才会出现此问题。

通常,即使对于没有文件上传的表单,我都将enctype设置为multipart / form-data时,也会遇到相同的错误。

使用此依赖项:

<dependency>
  <groupId>org.synchronoss.cloud</groupId>
  <artifactId>nio-multipart-parser</artifactId>
  <version>...</version>
</dependency>

尝试在表单中包含隐藏的csrf输入,还尝试将其附加到url上但出现相同的错误

    <form  method="post" th:action="${'/add/' + id + '/documents?' + _csrf.headerName + '=' + _csrf.token}"  enctype="multipart/form-data">
        <input type="file" name="documents" multiple="multiple">
        <input  type="hidden"
                th:name="${_csrf.headerName}"
                th:value="${_csrf.token}" />
        <input type="hidden" name="_csrf" th:value="${_csrf.token}">
        <button class="btn btn-success btn-l">Upload</button>
    </form>

对于csrf注入有这样的控制器建议

@ControllerAdvice
public class SecurityAdvice {@ModelAttribute("_csrf")Mono<CsrfToken> csrfToken(final ServerWebExchange exchange) {
    final Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(org.springframework.security.web.server.csrf.CsrfToken.class.getName(), Mono.empty());
    return csrfToken;
}

出于安全考虑,我有以下bean:

 @Bean
    public ServerCsrfTokenRepository csrfTokenRepository() {
        WebSessionServerCsrfTokenRepository repository =
                new WebSessionServerCsrfTokenRepository();
        repository.setHeaderName("X-CSRF-TK");

        return repository;
    }

并在SecurityWebFilterChain中像这样使用它:

.and().csrf().csrfTokenRepository(csrfTokenRepository())

更新:

为一些URL禁用csrf也足够。找到了一些示例,但所有示例均基于Servlet版本。 https://sdqali.in/blog/2016/07/20/csrf-protection-with-spring-security-and-angular-js/

2 个答案:

答案 0 :(得分:1)

看看Spring Security的官方建议:https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-multipart

基本上有两种方法可以做到这一点:(1)将MultipartFilter放在Spring Security过滤器之前,以及(2)像执行操作一样将CSRF令牌包含在form动作中。第一个选项是推荐的选项:

  

第一个选项是确保指定了MultipartFilter   在Spring Security过滤器之前。指定MultipartFilter   在Spring Security过滤器之前表示没有授权   用于调用MultipartFilter,这意味着任何人都可以放置   服务器上的临时文件。但是,只有授权用户才能   能够提交由您的应用程序处理的文件。在   通常,这是推荐的方法,因为临时文件   上传对大多数服务器的影响应该忽略不计。

要确保在具有Java配置的Spring Security过滤器之前指定了MultipartFilter,用户可以如下所示覆盖beforeSpringSecurityFilterChain:

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
        insertFilters(servletContext, new MultipartFilter());
    }
}

要确保在具有XML配置的Spring Security过滤器之前指定MultipartFilter,用户可以确保将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>

请注意,如果您仍要使用表单操作,则可能会泄漏查询参数。尝试将您的“ headerName”更改为“ parameterName”:

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

编辑:如果您无法切换到基于Servlet的容器​​(例如Jetty或Tomcat),并且表单操作建议不起作用,那么最近有Stack Overflow thread讨论这个问题。

其中一位开发人员报告说使用AJAX解决此问题:

  

我通过以下方法解决了这个问题:

     
      
  • 使用香草javascript发送多部分文件,例如   Mozilla's guide
  •   
  • 在元数据的HTML标头中添加_csrf令牌   标签,例如sending the CSRF token with Ajax
  • 的Spring指南中   
  • 代替使用jquery,将其直接添加到XHR对象中
  •   
     

var csrfToken = $("meta[name='_csrf']").attr("content"); var csrfHeader = $("meta[name='_csrf_header']").attr("content"); XHR.setRequestHeader(csrfHeader, csrfToken); XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary); XHR.send(data);

同一位开发人员reported this issue to Spring,但尚未引起任何关注。

答案 1 :(得分:0)

您只需在过滤器链中启用它即可。

来自documentation的引用:

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    http
        // ...
        .csrf(csrf -> csrf.tokenFromMultipartDataEnabled(true))
    return http.build();
}