【问题标题】:Spring Boot + Security - Can't upload file (Multipart) when CSRF is enabledSpring Boot + Security - 启用 CSRF 时无法上传文件(多部分)
【发布时间】:2015-10-16 07:49:49
【问题描述】:

我想从我的 angularJS 客户端上传文件。我启用了 CSRF 保护,它工作正常,除了当我尝试上传文件时,我得到一个 403 错误:

在请求参数“_csrf”上发现无效的 CSRF 令牌“null” 或标题“X-XSRF-TOKEN”。

但是令牌在请求标头中并且是正确的!当我禁用 CSRF 保护时,我可以毫无问题地上传文件。

除此之外,CSRF 保护工作正常。

这是我目前的配置:

SecurityConfiguration.java

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private SecurityUserDetailsService securityUserDetailsService;
    @Autowired
    private AuthFailureHandler authFailureHandler;
    @Autowired
    private AjaxAuthSuccessHandler ajaxAuthSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .exceptionHandling()
                .authenticationEntryPoint(authFailureHandler)
                .and()
                .authorizeRequests()
                .antMatchers(
                        "/",
                        "/index.html",
                        "/styles/**",
                        "/bower_components/**",
                        "/scripts/**"
                ).permitAll().anyRequest()
                .authenticated()
                .and().formLogin().loginPage("/login").permitAll()
                .and().logout().logoutUrl("/logout").logoutSuccessHandler(ajaxAuthSuccessHandler).permitAll()
                .and().httpBasic()
                .and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
                .csrf().csrfTokenRepository(csrfTokenRepository());
    }

    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(securityUserDetailsService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }

}

CsrfHeaderFilter.java

public class CsrfHeaderFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        if (csrf != null) {
            Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
            String token = csrf.getToken();
            if (cookie == null || token != null && !token.equals(cookie.getValue())) {
                cookie = new Cookie("XSRF-TOKEN", token);
                cookie.setPath("/");
                response.addCookie(cookie);
            }
        }
        filterChain.doFilter(request, response);
    }
}

最后我添加了一个 SecurityWebApplicationInitializer,正如 here 所指出的那样。

SecurityWebApplicationInitializer.java

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

}

我做错了什么?

更新:我添加了这个配置,但我仍然得到 403。

@Configuration
public class MultipartUploadConfig {

    @Bean(name = "filterMultipartResolver")
    public MultipartResolver multipartResolver() {
        return new CommonsMultipartResolver();
    }
}

【问题讨论】:

标签: java spring spring-security multipart


【解决方案1】:

您可以执行以下操作以在每次 Ajax 调用时提供令牌:

var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
    xhr.setRequestHeader(header, token);
});

还有另一个 StackOverflow 帖子谈到了这一点,这是我找到的,但我已经有一段时间没有看到它了。

【讨论】:

    【解决方案2】:

    如果你使用Ajax,将token参数放在url中,在form action中执行:

    url: "/uploadFile?${_csrf.parameterName}=${_csrf.token}"
    

    变成这样:

    function uploadFile() {
          $.ajax({
            url: "/uploadFile?${_csrf.parameterName}=${_csrf.token}",
            type: "POST",
            data: new FormData($("#upload-file-form")[0]),
            enctype: 'multipart/form-data',
            processData: false,
            contentType: false,
            cache: false,
            success: function () {
              // Handle upload success
              $("#upload-file-message").text("File succesfully uploaded");
            },
            error: function () {
              // Handle upload error
              $("#upload-file-message").text(
                  "File not uploaded (perhaps it's too much big)");
            }
          });
        }
    

    【讨论】:

    【解决方案3】:

    如果您可以将 CSRF 令牌作为 url 参数发送,这是一个快速的解决方案。在您的 html 模板中。

    <form .... th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}">
    ...
    </form>
    

    【讨论】:

    • @ChristosBaziotis:他们正在回答狂欢 -> same answer.
    • 这会暴露 URL 中的令牌,不好的做法
    猜你喜欢
    • 2017-11-05
    • 2014-10-31
    • 2016-07-21
    • 2014-05-01
    • 2022-10-22
    • 1970-01-01
    • 2015-11-09
    • 2014-09-30
    相关资源
    最近更新 更多