Tomcat文件上传你可能不知道的事情

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

前言

我们在文件上传时,使用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工具直接拖动到指定目录。

Tomcat文件上传你可能不知道的事情
image.png

在通过下面命令运行。

java -jar SpringDemo-0.0.1-SNAPSHOT.jar --server.port=9696
Tomcat文件上传你可能不知道的事情
image.png

但由于是云服务器,还需要开放端口,但服务器上有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文件上传你可能不知道的事情