Webflux multipart/form-data,启用 csrf,有和没有文件上传获取 Invalid CSRF Token
Posted
技术标签:
【中文标题】Webflux multipart/form-data,启用 csrf,有和没有文件上传获取 Invalid CSRF Token【英文标题】:Webflux multipart/form-data, csrf enabled, with and without file upload getting Invalid CSRF Token 【发布时间】:2019-02-04 01:59:52 【问题描述】:禁用 csrf 后,我可以上传文件,但我需要启用它。 只有当表单 enctype 是 multipart/form-data 时才会出现问题,即 'Invalid CSRF Token' with 403。
通常当我将 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/
【问题讨论】:
【参考方案1】:您可以简单地在过滤器链中启用它。
来自documentation的参考:
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
http
// ...
.csrf(csrf -> csrf.tokenFromMultipartDataEnabled(true))
return http.build();
【讨论】:
【参考方案2】:看看Spring Security的官方推荐:https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-multipart
基本上有两种方法:(1)将 MultipartFilter 放在 Spring Security 过滤器之前,(2)在表单操作中包含 CSRF 令牌,就像你正在做的那样。第一个选项是推荐的:
第一个选项是确保指定 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());
为确保MultipartFilter在带有XML配置的Spring Security过滤器之前被指定,用户可以确保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 解决了该问题:
我通过以下方式解决了这个问题:
使用 vanilla javascript 发送多部分文件,例如 Mozilla's guide 在 HTML 标头中添加 _csrf 标记,在 meta 标签,例如 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,但尚未引起任何关注。
【讨论】:
嗨 Fabio,Webflux 中没有 ServletContext,因为它是基于 Netty 的。 您可以使用 Jetty 或 Tomcat 代替,对吗? Webflux 支持它们。但是即使使用 Netty,您是否尝试过从 headerName 切换到 parameterName? 已经用 parameterName 试过了。它不工作。由于 Webflux 的默认服务器是 Netty,我正在寻找答案。基于 Servlet 的解决方案在网上很常见,但找不到适用于 Webflux-Netty 的解决方案。 我明白了。我会用更多替代方案更新答案。以上是关于Webflux multipart/form-data,启用 csrf,有和没有文件上传获取 Invalid CSRF Token的主要内容,如果未能解决你的问题,请参考以下文章