使用签名URL上传文件的一个注意事项

使用签名URL上传文件时,需要特别小心处理请求的Content-Type头

如果后端用阿里云OSS SDK生成了签名URL,前端直接使用签名URL上传文件时,为了避免把文件传给后端服务器占据内存,常见的实践是,前端把文件类型传给后端,后端根据文件类型生成带Content-Type头的签名URL,前端直接使用签名URL上传文件。

这里有一个问题,如果我们不手动指定后端签名使用的Content-Type头,那么后端签名时会认为请求不具有Content-Type头。但是前端在使用签名URL进行上传时,浏览器会自动设置Content-Type头为指定的文件类型,就会导致OSS服务器校验签名URL失败,返回403错误。

所以,后端在生成签名URL时,必须手动指定Content-Type头。最佳的实践是,前端把文件的MIME类型传给后端,后端根据MIME类型生成带Content-Type头的签名URL。

后端:

 1const oss = require('ali-oss')
 2const express = require('express')
 3const app = express()
 4const client = new oss({
 5    accessKeyId: 'yourAccessKeyId',
 6    // ...
 7    authorizationV4: true // 使用V4签名
 8})
 9
10app.use(express.json())
11
12app.post('/get-signed-url', (req, res) => {
13    const { type } = req.body
14    const ossPath = 'yourObjectName'  // 上传到OSS的文件路径
15    const url = await client.signatureUrlV4(
16        'PUT',
17        20, // 有效期20秒
18        {
19            headers: {
20                'Content-Type': type  // 签名中指定Content-Type头
21            }
22        },
23        ossPath
24    )
25    res.json({ url })
26})

前端:

 1const file = document.querySelector('input[type="file"]').files[0]
 2const type = file.type || 'application/octet-stream'  // 获取文件的MIME类型
 3const url = await fetch('/get-signed-url', {
 4    method: 'POST',
 5    body: JSON.stringify({ type })
 6})
 7const { url } = await response.json()
 8
 9const upload = await fetch(url, {
10    method: 'PUT',
11    body: file,
12    headers: {
13        'Content-Type': type  // 必须保证跟签名的Content-Type头一致
14    }
15})

这样就能保证上传的文件类型与签名URL的Content-Type头一致,不会出现403错误。

另外,我发现在签名URL中附带Authorization头,是无效操作,猜测Authorization头会被OSS服务器忽略。后端即使在签名时指定了Authorization头,前端不携带Authorization头也能上传成功。