一、背景
S3 Presign URL是通过S3服务生成一个带签名的URL,这个URL包含了S3桶名、文件名(目录名)、所属账号、时间戳、有效期等信息。当在有效期内的时候,通过Post请求发送到这个URL,可以上传/下载。当有效期超过之后,访问此文件,会提示403没有权限。
使用Presign URL的好处是安全:
- 调用API接口时候,只暴露Access Key ID,而不暴露Secret Key,也就是不暴露API的密码。
- Presign URL可设置过期时间,例如生成URL后限制60秒,那么上传操作必须在60秒内完成,60秒后之前生成的URL和时间戳过期。
S3 Presign URL的原理讲解:https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/PresignedUrlUploadObject.html
使用Python SDK调用Presign URL:https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-presigned-urls.html
二、生成Presign URL
生成Presign URL是必须在后端完成,需要与服务器和AWS S3进行交互。因此一般可以定义一个服务或接口,专门用于生成S3 Presign URL,要上传前调用这个接口,获得特定权限。调用前,要事先人为指定文件名,S3要求基于一个确定的文件名来生成签名。
本实验使用Python3代码模拟服务器端的行为。为了运行本程序,需要安装Python 3.8的request库,用于向构建的URL发起POST操作。安装命令如下:
pip install requests
编写如下脚本,此脚本要求本级安装有AWS CLI,能正常执行CLI工具。将如下内容中S3桶名称替换为实际使用的痛名称,将文件名替换为要上传的文件名,最后保存为 generateURL.py
。
import boto3
import requests
import json
bucket_name = "s3-js-upload-demo"
object_name = "upload-file-01.jpg"
def create_presigned_post(bucket_name, object_name,
fields=None, conditions=None, expiration=120):
s3_client = boto3.client('s3')
response = s3_client.generate_presigned_post(bucket_name,
object_name,
Fields=fields,
Conditions=conditions,
ExpiresIn=expiration)
return response
response = create_presigned_post(bucket_name, object_name)
url = response['url']
fileds = response['fields']
print(url)
print(fileds)
在Console下运行,返回结果如下。
# lxy @ myMBP in ~ [9:47:43]
$ /usr/local/bin/python3 /Users/lxy/Documents/AWS/MyWorkshop/S3_PreSign-Upload/generateURL.py
https://s3-js-upload-demo.s3-accelerate.amazonaws.com/
{'key': 'upload-file-01.jpg', 'AWSAccessKeyId': 'AKIAR57Y4KKLEJSU5S5O', 'policy': 'eyJleHBpcmF0aW9uIjogIjIwMjAtMTAtMjBUMDE6NDk6NDVaIiwgImNvbmRpdGlvbnMiOiBbeyJidWNrZXQiOiAiczMtanMtdXBsb2FkLWRlbW8ifSwgeyJrZXkiOiAidXBsb2FkLWZpbGUtMDEuanBnIn1dfQ==', 'signature': '6+vDtE7dwdzSPlkOXLQa4Reeb8o='}
在如下一段返回结果中,第一行的地址是URL,也就是调用S3的Endpoint终端节点。然后分别返回的字段包括key(文件名)、AWSAccessKeyId(API Access Key ID)、policy(对S3操作策略)、signature(签名)等。在后续的代码调用中都需要传入给S3。
接下来我们分别从后台、前台上传测试。
三、从后台上传测试
在上传过程时候,可以从客户端的前端如HTML/Javascript等多种方式发起Post操作上传。也可以使用Java/Python等后台程序完成。
以Python为例,编写如下脚本,并另存为 uploadToS3.py
,保存后,修改其中要上传的文件名。
import boto3
import requests
import json
object_name = 'upload-file-01.jpg'
url = "https://s3-js-upload-demo.s3-accelerate.amazonaws.com/"
fileds = {'key': 'upload-file-01.jpg', 'AWSAccessKeyId': 'AKIAR57Y4KKLEJSU5S5O', 'policy': 'eyJleHBpcmF0aW9uIjogIjIwMjAtMTAtMjBUMDE6NDk6NDVaIiwgImNvbmRpdGlvbnMiOiBbeyJidWNrZXQiOiAiczMtanMtdXBsb2FkLWRlbW8ifSwgeyJrZXkiOiAidXBsb2FkLWZpbGUtMDEuanBnIn1dfQ==', 'signature': '6+vDtE7dwdzSPlkOXLQa4Reeb8o='}
with open(object_name, 'rb') as f:
files = {'file': (object_name, f)}
http_response = requests.post(url, fileds, files=files)
print(http_response)
执行后返回结果如下。
# lxy @ 8c85905f3ef5 in ~/Documents/AWS/MyWorkshop/S3_PreSign-Upload [9:48:32]
$ /usr/local/bin/python3 /Users/lxy/Documents/AWS/MyWorkshop/S3_PreSign-Upload/uploadToS3.py <aws:sgp>
/Users/lxy/Documents/AWS/MyWorkshop/S3_PreSign-Upload
<Response [204]>
如果返回代码是204,这表示上传成功。如果返回结果是403,则表示Post过去的URL地址、Token等拼接的信息不对,无权限写入。
如果报告File Not Found,则是Python运行路径的问题。例如在MacOS上VSCode等开发工具内调试的时候,系统自动的路径其实是 /Users/xxx
这种用户Home目录,因此运行时候会发现要上传的图片不在系统Home路径下,就无法读取。解决办法二选一:
- 把代码里边改为绝对路径,例如 /Users/abc/Documents/code/xxxx.jpg
- 或者在开发工具自带的Terminal下切换下路径,执行
cd /Users/abc/Documents/code/
进入代码和图片所在的路径,再次从VSCode上执行,就可以读取到文件。
上传完毕后,去S3存储桶内即可看到文件。注意在S3 Presign URL的过期时间有效期内,可以反复多次上传,后续上传会覆盖原始文件。
四、从前台上传测试
客户端前端上传,可以用任何一种语言,只要构造POST请求发到刚才获取的PreSign URL地址就可以了。
例如HTML构建如下网页:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<!-- Copy the 'url' value returned by S3Client.generate_presigned_post() -->
<form action="https://s3-js-upload-demo.s3-accelerate.amazonaws.com/" method="post" enctype="multipart/form-data">
<!-- Copy the 'fields' key:values returned by S3Client.generate_presigned_post() -->
<input type="hidden" name="key" value="upload-file-01.jpg" />
<input type="hidden" name="AWSAccessKeyId" value="AKIAR57Y4KKLEJSU5S5O" />
<input type="hidden" name="policy" value="eyJleHBpcmF0aW9uIjogIjIwMjAtMTAtMTRUMTA6MDY6NDRaIiwgImNvbmRpdGlvbnMiOiBbeyJidWNrZXQiOiAiczMtanMtdXBsb2FkLWRlbW8ifSwgeyJrZXkiOiAidXBsb2FkLWZpbGUtMDEuanBnIn1dfQ==" />
<input type="hidden" name="signature" value="drAiaex/Q18YOug6rUSKm5zr9Zg=" />
File:
<input type="file" name="file" /> <br />
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</body>
</html>
在这段HTML代码中,包含了隐藏的hidden属性,需要替换其中的 policy
和 signature
为前文获得的参数,然后保存为 upload.html
。在开发者本地可以用浏览器直接打开这个upload.html,即可测试上传。
测试过程注意,前文生成的Presign URL的时间有效期是120秒,因此如果修改html、保存到本地等做测试过程超过120秒,会返回403无权限。在实际代码开发过程中,上传html的界面上的参数是通过接口自动带出来,因此只需要按照上传文件的大概体积,给一个适当的上传超时的时间即可。
上传完毕后,去S3存储桶内即可看到文件。注意在S3 Presign URL的过期时间有效期内,可以反复多次上传,后续上传会覆盖原始文件。
五、结论
S3 Presign URL是由服务器端与AWS S3服务生成的包含临时访问地址,可以在有效时间内完成上传操作。生成的Presign URL可以被直接输出为HTML,也可以被放入到Javascript中进行页面纯前端的调用。
当在页面前端使用S3 Presign URL去Post上传文件时候,网络流量从网页直接上传到AWS S3,而不经过后台web服务器。降低了Web服务器的负载。配合S3存储桶的全球加速功能,可以实现快速的各国家就近接入加速上传。
完。