Tomcat文件上传你可能不知道的事情
前言
我们在文件上传时,使用SpringBoot提供的MultipartFile很快接编写完了,但是有没有考虑这样一个问题,通常系统都有认证,对吧,也有可能文件上传的接口是单独的认证流程,也就是系统对文件上传接口开放,在文件上传接口中通过form表单中的各种token进行验证,和系统的认证分离。
但问题不在这里。
我们把Tomcat现在当成底层,把SpringBoot当成应用层,一个请求首先会进入Tomcat,但是当文件上传时候,Tomcat会先保存上传的文件,然后在交给应用层,所以应用层读取的文件流是Tomcat生成好的,我们不直接从Socket中读取。
问题在Tomcat接收文件的时候,并没有认证,证阶段也是由应用层控制的,所以这时候任何人都可以直接上传,这个文件最后被怎么样了,由应用层决定,Tomcat在之后会删除掉他读取的文件,所以安全影响不大,但是速度慢了啊,你想,一个用户在没权限的时,他总能把文件上传到服务,但是这个请求由应用层在判断没权限时,也不会保存,Tomcat也会删掉,是不是就浪费了一点时间和流量。
这个问题还不好解决,拿CSDN和简书来说,都是这样,当你调用他们文件上传接口时,这期间除了你的上传网速,还有他们也在读取你的文件,之后他们才会验证参数,我们想要的是,能不能先验证,在读取文件?
当然有了,只不过我们作为研究,这种方案到底在实际中行不行,不知道,但是可以实现。
下面做一个试验,首先写一个upload接口。
@RestController
class FIleController {
@PostMapping("upload")
fun upload(@RequestParam file: MultipartFile): String {
file.transferTo(Paths.get("./text.data"))
return "OK"
}
}
在编写一个拦截器,用来认证,这里只简单的判断请求头中token是否为111。
@Configuration
class WebMvcConfig : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
super.addInterceptors(registry)
registry.addInterceptor(object : HandlerInterceptor {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
println(request.getPart("file"))
return request.getHeader("token")=="111"
}
}).addPathPatterns("/**")
}
}
但是最好不要在本地测试,本地速度太快,不好判断,我们把他上传到服务器,如下,可以通过CoolDesktop工具直接拖动到指定目录。

在通过下面命令运行。
java -jar SpringDemo-0.0.1-SNAPSHOT.jar --server.port=9696

但由于是云服务器,还需要开放端口,但服务器上有nginx,我们做一下转发。
location /test/ {
proxy_pass http://localhost:9696/;
}
我们打开postman进行测试,结果在上传一个10M的文件时,用了大概9秒,无论是否在请求头中增加token=111,都是如此,而我想要的是,在请求头中token不为111或为空时,直接返回。
怎么做呢?其实很简单,需要扩展Tomcat,因为SpringBoot内嵌时,不能自定义Valve,这需要借助Tomcat的Valve功能实现,在Tomcat读取文件时,抢先一步认证请求,如果应用是war类型,可以在Tomcat的server.xml下配置。
但我已经测试过了,修改起来也比较简单,在Tomcat的engine启动时候,增加一个TomcatGlobalAuthenticationValve,这个Valve用来认证,源码可以到 https://github.com/houxinlin/desktop-tomcat 这里查看
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
pipeline.addValve(new TomcatGlobalAuthenticationValve());
但是这时候不能再拿nginx做代理,因为nginx同样会先读取文件,需要直接访问Tomcat。
替换SpringBoot中的原本tomcat。
implementation(files("/home/HouXinLin/project/java/tomcat/desktop-tomcat/apache-tomcat-9.0.58-src/output/embed/tomcat-embed-core.jar"))
implementation("org.springframework.boot:spring-boot-starter-web"){
exclude(group="org.apache.tomcat.embed",module = "tomcat-embed-core")
}
这回可以再次测试,就可以发现在没认证的情况下,只需要1秒就返回失败,加上认证需要9秒多,和我们期望的一样。
- END -
原文始发于微信公众号(十四个字节):Tomcat文件上传你可能不知道的事情