[英]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);
}
}
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 - 无法处理部件,因为没有提供多部件配置
CommonsMultipartResolver
beanStandardServletMultipartResolver
beanMultipartConfigElement
添加到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 属性来驱动它allowCasualMultipartParsing="true"
选项(没有测试),我不想坚持它的 Tomcat 特定http.authorizeRequests().antMatchers("/**").permitAll();
仍然是唯一的安全上下文配置,所以不要认为它的安全上下文配置问题beforeSpringSecurityFilterChain(ServletContext servletContext)
中的MultipartFilter
中显式设置多部分解析器 bean 名称,但仍然没有运气_csrf
令牌添加到请求 header 对这两种情况都不起作用SecurityInitializer
构造函数中错过了额外WebAppContext
class 。 现在错误 500 消失了,但案例 1 出现了 403。记录显示我有无效csrf
令牌,尽管我像上面一样将它添加到 headercsrf
令牌提交表单<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.