繁体   English   中英

尽管配置完整,但无法使用 Spring 安全性将文件上传到 Spring MVC

[英]Unable to upload file to Spring MVC with Spring Security despite full configuration

I'm trying to upload.pdf file with jQuery AJAX to Spring MVC 5 with Spring Security 5 back-end running on Tomcat and faced multiple issues depending on Spring configuration

笔记:

文件上传应无需身份验证即可使用

前端

标记:

<div id="upload-modal" class="modal">
    <div class="modal-content">
        <h4>Upload</h4>
        <form action="#" enctype="multipart/form-data">
            <div class="file-field input-field">
                <div class="btn">
                    <span>View...</span>
                    <input type="file" name="file" accept="application/pdf">
                </div>
                <div class="file-path-wrapper">
                    <label>
                        <input class="file-path validate" type="text">
                    </label>
                </div>
            </div>
        </form>
    </div>
    <div class="modal-footer">
        <a href="#" class="modal-close waves-effect waves-green btn-flat">Cancel</a>
        <a href="#" id="upload-bttn" class="waves-effect waves-light btn-flat btn">Upload</a>
    </div>
</div>

所有请求的csrf header:

$(document).ready(function () {
    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);
    });
});

使用 jQuery AJAX 上传:

$("#upload-bttn").click(function () {
    var $uploadModal = $("#upload-modal");
    const fileName = $uploadModal.find(".file-path").val();
    const extension = fileName.substr(fileName.lastIndexOf(".") + 1);
    if (extension === "pdf") {
        $.ajax({
            url: "/upload",
            type: "POST",
            data: new FormData($uploadModal.find("form").get(0)),
            processData: false,
            contentType: false,
            success: function () {
                console.log("success")
            },
            error: function () {
                console.log("error")
            }
        });
    } else {
        M.toast({html: 'Selected file is not .pdf'});
    }
});

后端

一般配置如下所示。 根据情况修改

安全初始化:

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {

    public SecurityInitializer() {
        super(SecurityContext.class);
    }

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

}

应用程序初始化:

public class ApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {

        servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));
        servletContext.getSessionCookieConfig().setHttpOnly(true);
        servletContext.getSessionCookieConfig().setSecure(true);

        AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
        dispatcherServlet.register(WebAppContext.class);

        ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
    }
}

案例 1 - CommonsMultipartResolver bean 定义

CommonsMultipartResolver bean 定义:

@Bean
public CommonsMultipartResolver multipartResolver(
        @Value("${max.upload.size}") Integer maxNumber,
        @Value("${max.size}") Integer maxSize) {

    CommonsMultipartResolver resolver = new CommonsMultipartResolver();
    resolver.setMaxUploadSize(1024 * maxSize * maxNumber);
    resolver.setMaxUploadSizePerFile(maxSize);
    resolver.setMaxInMemorySize(maxSize);
    resolver.setDefaultEncoding("UTF-8");
    try {
        resolver.setUploadTempDir(new FileSystemResource(System.getProperty("java.io.tmpdir")));
    } catch (IOException e) {
        e.printStackTrace();
    }
    return resolver;
}

我记得当MultipartResolver bean 应明确命名为“multipartResolver”时,出现了奇怪的 Spring 行为。 我在上面的配置中尝试了@Bean@Bean("multipartResolver")并且得到了相同的结果(尽管上面的 bean 根据方法名称被命名为“multipartResolver”)

结果:

错误 500 - 无法处理部件,因为没有提供多部件配置

案例 2 - Servlet 注册表中的 MultipartConfigElement

  • 删除CommonsMultipartResolver bean
  • 添加了StandardServletMultipartResolver bean
  • MultipartConfigElement添加到ApplicationInitializer

StandardServletMultipartResolver bean 定义:

@Bean
public StandardServletMultipartResolver multipartResolver() {
    return new StandardServletMultipartResolver();
}

更新了ApplicationInitializer

@Override
public void onStartup(ServletContext servletContext) {
    ...
    servlet.setMultipartConfig(new MultipartConfigElement(
            System.getProperty("java.io.tmpdir")
    ));
}

根据 Spring 文档:

确保在 Spring 安全过滤器之前指定 MultipartFilter。 在 Spring 安全过滤器之后指定 MultipartFilter 意味着没有调用 MultipartFilter 的授权,这意味着任何人都可以在您的服务器上放置临时文件。 但是,只有授权用户才能提交由您的应用程序处理的文件

因为我需要允许未经身份验证的用户上传我在SecurityInitializer之前之后尝试过的文件,如下所示,结果相同

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

或者

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

结果:

错误 403

问题

  • 我在配置中错过了什么?

想法

  • CommonsMultipartResolver更可取,因为它允许使用 Spring 属性来驱动它
  • Spring 安全上下文设置有问题
  • 有一个allowCasualMultipartParsing="true"选项(没有测试),我不想坚持它的 Tomcat 特定

更新

  • 禁用 Spring 安全性一切正常
  • http.authorizeRequests().antMatchers("/**").permitAll(); 仍然是唯一的安全上下文配置,所以不要认为它的安全上下文配置问题
  • beforeSpringSecurityFilterChain(ServletContext servletContext)中的MultipartFilter中显式设置多部分解析器 bean 名称,但仍然没有运气
  • _csrf令牌添加到请求 header 对这两种情况都不起作用
  • 意识到我在SecurityInitializer构造函数中错过了额外WebAppContext class 。 现在错误 500 消失了,但案例 1 出现了 403。记录显示我有无效csrf令牌,尽管我像上面一样将它添加到 header
  • 尝试使用包含隐藏输入的csrf令牌提交表单<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>但结果相同 - 错误 403无效的令牌语句

经过两天的挣扎:

构造函数应包含安全和应用程序上下文配置类

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {

    public SecurityInitializer() {
        super(SecurityContext.class, WebAppContext.class);
    }

}

应用程序上下文 ( WebAppContext ) 应该包含MultipartResolver bean 定义

@Bean
public CommonsMultipartResolver multipartResolver(
        @Value("${max.upload.size}") Integer maxNumber,
        @Value("${max.size}") Integer maxSize) {

    CommonsMultipartResolver resolver = new CommonsMultipartResolver();
    resolver.setMaxUploadSize(1024 * maxSize * maxNumber);
    resolver.setMaxUploadSizePerFile(maxSize);
    resolver.setMaxInMemorySize(maxSize);
    resolver.setDefaultEncoding("UTF-8");
    try {
        resolver.setUploadTempDir(new FileSystemResource(System.getProperty("java.io.tmpdir")));
    } catch (IOException e) {
        e.printStackTrace();
    }
    return resolver;
}

In my case after application initialization csrf token inside Spring CsrfTokenRepository was empty for some reason so when Spring been comparing token from client request header with null in CsrfFilter Spring was returning error 403. I configured csrf in security context in the following way:

@Override
protected void configure(HttpSecurity http) throws Exception {
    ...
    http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository());
    ...    
}

现在csrf令牌在 cookies 中传递,服务器对浏览器的第一个响应和存储库生成并缓存一个令牌以与来自客户端的令牌进行比较,因此比较成功

如果您想从 cookie 中获取令牌并将其设置为csrf header,这里CookieCsrfTokenRepository也可以声明为CookieCsrfTokenRepository.withHttpOnlyFalse() ,但我选择使用上述元标记方法的 go

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM