spring - Webflux multipart/form-data,启用 csrf,有和没有文件上传获取 Invalid CSRF Token
问题描述
禁用 csrf 后,我可以上传文件,但我需要启用它。仅当表单 enctype 为 multipart/form-data 时才会出现此问题,即带有 403 的“Invalid CSRF Token”。
通常,即使对于没有文件上传的表单,我将 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/
解决方案
看看 Spring Security 的官方推荐:https ://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-multipart
基本上有两种方法:(1)将 MultipartFilter 放在 Spring Security 过滤器之前,(2)在表单操作中包含 CSRF 令牌,就像你正在做的那样。第一个选项是推荐的:
第一个选项是确保在 Spring Security 过滤器之前指定 MultipartFilter。在 Spring Security 过滤器之前指定 MultipartFilter 意味着没有调用 MultipartFilter 的授权,这意味着任何人都可以在您的服务器上放置临时文件。但是,只有授权用户才能提交由您的应用程序处理的文件。一般来说,这是推荐的方法,因为临时文件上传对大多数服务器的影响可以忽略不计。
为了确保 MultipartFilter 在 Spring Security 过滤器之前指定 java 配置,用户可以覆盖 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 线程讨论此问题。
一位开发人员报告说使用 AJAX 解决了这个问题:
我通过以下方式解决了这个问题:
- 使用 vanilla javascript 发送多部分文件,就像在 Mozilla 的指南中一样
- 在 HTML 标头中的元标记中添加 _csrf 标记,就像在 Spring 指南中使用 Ajax 发送 CSRF 标记一样
- 而不是使用 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);
同一位开发人员向 Spring 报告了此问题,但尚未引起任何关注。
推荐阅读
- css - 如何按单个单元格而不是内容最多的单元格来调整 CSS 网格行的大小
- python - 如何摆脱 django DecimalField 中的多余零?
- c - 如何使用带有 sigint 的标志在一秒钟内发送两次时发出通知?
- vue.js - 为什么每次其他卡片属性更改时 Vuetify 都会重新加载图像?
- reactjs - ReactDom.hydrate 移除 SSR DOM
- node.js - Firebase 云消息传递 android 优先级属性
- java - 什么是 Java 中的“系统线程”?
- json - row_to_json 将“NULL”导出为“None”
- .htaccess - mod_rewrite 规则不起作用,但我的语法正确
- javascript - Blazor - 动态创建选择框