签名URL
浏览器打开的网页(我们的网站)想要支持用户传文件到服务端,一种办法就是通过http给到服务端,服务端在把文件给到单独的OSS服务。这个过程会经历一次中转效率低。文件大且多的时候,就得考虑应用服务的带宽负载,就无法直接使用上OSS的伸缩架构能力。
我们可以让网页直接把文件传给OSS服务,但是如果把OSS的账户密码给到网页使用是不安全的,这样阿里云官网提供了方案授权给第三方上传_对象存储(OSS)-阿里云帮助中心 (aliyun.com),我们要用一下其中的“签名URL”方式。
阿里云OSS的签名URL就支持get、put两种,get是下载文件,put是上传文件。
一、文件上传
服务端通过OSS SDK(比如Go语言的)生成一个上传文件用的“签名URL”,这样客户端可以凭借URL,不依赖OSS SDK直接上传文件。
这个URL在签名的时候设置了有效时长,而且一旦被使用过,URL就失效,确保了只能用一次。
签名是支持带可选参数的,如果服务端签URL的时候设置了content-type,比如 text/plain,那么客户端请求URL的时候其content-type也必须是text/plain。
// 如果要在前端使用带可选参数的签名URL,请确保在服务端生成该签名URL时设置的ContentType与在前端使用时设置的ContentType一致。
options := []oss.Option{
oss.Meta("myprop", "mypropval"),
oss.ContentType("text/plain"),
}
signedURL, err = bucket.SignURL(objectName, oss.HTTPPut, 60, options...)
if err != nil {
HandleError(err)
}
fmt.Printf("Sign Url:%s\n", signedURL)
1、注意事项:content-type用不了multiple/form-data
用multiple/form-data的方式传文件是不行的,因为这个header头实际签名内容是不固定的。
比如
curl -k --location --request PUT --X PUT 'https://xxxxxx/1.txt?Expires=1709709827&OSSAccessKeyId=xxxxx&Signature=xxxxx' \
--form 'file=@C:\Users\taiwu\Desktop\1.txt'
请求后会得到报错信息,从报错中能看到服务端实际签名的原始字符串如下,这里面关于header的boundary=是动态的。
<StringToSign>PUT
multipart/form-data; boundary=------------------------4452e35e5d6756d1
1709709827
/taiwu/1.txt</StringToSign>
2、注意事项:签名时没有content-type的情况
如果签名的时候没有设置content-type,那么客户端请求这个签名URL的时候也不能设置content-type,即不传content-type内容,传了也会报错。
这时候我们可以下面的curl模拟不到content-type的请求
curl -k -T 1.txt 'https://xxxxxx/1.txt?Expires=1709710727&OSSAccessKeyId=xxxxxx&Signature=xxxxxx'
3、文件是图片的情况
curl -k -T 111.jpg \
-H "Content-Type: image/jpeg" \
'https://xxxxxxxxxxxxxxxxxxxx'
我们要上传的如果是jpg图片,可以设置Content-Type为 image/jpeg, 用这个参数包含到签名过程中。
上面演示了如何用curl上传一个图片到指定的签名url地址。
这样上传的图片,当我们用浏览器访问的时候,服务端会返回文件类型是image/jpeg,浏览器能直接预览。
如果我们Content-Type不用,那么这样传的图片,后续用浏览器直接访问的时候,返回的内容类型是application/octet-stream,文件直接被下载了。
二、文件下载
签名URL是用get方式获取文件,对于下载用的url,签名的时候会有content-type,用的什么,那么下载时客户端(浏览器、curl等)调用的时候也必须使用同样的content-type,否则就会提示签名错误。
我们可以用curl -O 命令来下载文件,-O代表文件存的名称来自url,最后得到文件。
1、注意事项:请求content-type和签名时的必须一致
我们签名URL的时候,content-type不设置,那么用curl请求的时候,也不要带上content-type信息。比如用下面的curl就不带content-type,这样请求和签名URL的content-type都是空,是对的。
curl -k --request GET -O \
'https://xxxxxx/1.txt?Expires=1709717832&OSSAccessKeyId=xxxxxx&Signature=xxxxxx'
如果后台签名URL的时候用了特定的content-type,比如 octet-stream, 那么客户端请求的时候也必须带上这个头信息。比如我们用上面的curl方式请求,就会拿到错误信息,看签名内容,可以发现这么请求时签名里面没用content-type内容,但是服务端签名时字符串是有octet-stream的,这样自然对不上。
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<RequestId>65E8284CE11F2694B961FA64</RequestId>
<HostId>xxxxxxxxxxx</HostId>
<OSSAccessKeyId>xxxxxxxxx</OSSAccessKeyId>
<SignatureProvided>xxxxxxxx</SignatureProvided>
<StringToSign>GET
1709718379
1.txt</StringToSign>
<StringToSignBytes>xxxxxxx</StringToSignBytes>
</Error>
我们改为正确的请求指令,带上一直的content-type就可以了
curl -k --request GET -O \
'https://xxxxxx/1.txt?Expires=1709718379&OSSAccessKeyId=xxxxxx&Signature=xxxxxx' \
--header 'Content-Type:octet-stream'
三、OSS签名URL和VUE框架配合
1、上传
上传文件的时候,vue通过axios的put请求模式的content-type是 application/json;charset=UTF-8,所以我们提前生成的签名URL,在签名的时候提供application/json;charset=UTF-8这样能保证一致。数据落到OSS上面最终还是一个文件形式。
2、下载
下载文件的时候,vue通过axios的get请求,其默认的content-type是application/json;charset=UTF-8,所以我们生成下载的签名url时需要提供同样的content-type。
用curl模拟如下
curl -k -O \
--header 'Content-Type:application/json;charset=UTF-8' \
'https://xxxxxx/1.txt?Expires=1709719345&OSSAccessKeyId=xxxxx&Signature=xxxxxx'