spring - Webflux multipart/form-data,启用 csrf,有和没有文件上传获取 Invalid CSRF Token

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

禁用 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 注入(inject)有这样的 Controller 建议
@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 token ,就像你正在做的那样。第一个选项是推荐的:

The first option is to ensure that the MultipartFilter is specified before the Spring Security filter. Specifying the MultipartFilter before the Spring Security filter means that there is no authorization for invoking the MultipartFilter which means anyone can place temporary files on your server. However, only authorized users will be able to submit a File that is processed by your application. In general, this is the recommended approach because the temporary file upload should have a negligble impact on most servers.



为确保 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 thread讨论这个问题。

一位开发人员报告说使用 AJAX 解决了这个问题:

I solved this problem by:

  • sending the multi-part file using vanilla javascript, like in Mozilla's guide
  • adding the _csrf token in the HTML header, in meta tags, like in the Spring guideline for sending the CSRF token with Ajax
  • instead of using jquery, adding it directly to the XHR object

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 ,但还没有引起注意。

关于spring - Webflux multipart/form-data,启用 csrf,有和没有文件上传获取 Invalid CSRF Token,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52074742/

相关文章:

javascript - Spring stomp - 使用 SimpMessagingTemplate 从服务器发送消息

java - 在 SpringBoot @Scheduled 中更新 Cron 表达式

grails - Grails,Spring Security-登录 Controller 的导入不起作用

spring-boot - 如何在服务测试类中模拟@Autowired HttpServletRequest?

java - 生产者无法将消息分发给多个消费者?

来自桌面应用程序的 url 重定向的 Grails spring 安全问题

java - Spring boot - 返回 403 Forbidden 而不是重定向到登录页面

java - Spring Restfull Jax-RS 注释支持

java - Spring Data JPA 无法确定属于同一类对象的属性的类型

Spring Boot启动监听器