如果业务中用到了 S3,那文件的上传下载必然是最基础的功能,但如果直接通过 Upload 方法上传文件,会存在非常大的安全隐患,所以普遍做法是后端提供文件上传预签名地址,前端根据预签名地址进行文件上传操作。
一、后端生成预签名地址
根据后端语言的不同种类,使用不同的 SDK,这里示例采用 golang 生成 S3 文件上传预签名地址:
package main
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"time"
)
var (
Client *s3.S3
AccessKeyId = "xxx"
SecretAccessKey = "xxx"
Region = "xxx"
)
type PutObjectInput struct {
Acl string
StorageClass string
Tagging string
Expires int64
ContentType string
ContentLength int64
ContentMd5 string
}
func main() {
if err := InitS3Client(AccessKeyId, SecretAccessKey, Region); err != nil {
fmt.Println("初始化 S3 操作的 session 错误", err)
return
}
fmt.Println(PutObjectPreSign("test", "/test/a.jpg", 300, &PutObjectInput{
Acl: "public-read",
ContentType: "image/jpg",
ContentLength: 12235,
}))
}
func InitS3Client(accessKeyId, secretAccessKey, region string) error {
// 初始化 S3 操作的 session
sess, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(accessKeyId, secretAccessKey, ""),
Region: aws.String(region),
})
if err != nil {
return err
}
// 初始化 s3 操作客户端
Client = s3.New(sess)
return nil
}
func PutObjectPreSign(bucket, key string, ttl int64, info *PutObjectInput) (url string, err error) {
// 1. 构建预处理信息
input := s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
ServerSideEncryption: aws.String("AES256"),
ACL: aws.String(info.Acl),
StorageClass: aws.String(info.StorageClass),
Tagging: aws.String(info.Tagging),
Expires: aws.Time(time.Unix(info.Expires, 0)),
ContentType: aws.String(info.ContentType),
ContentMD5: aws.String(info.ContentMd5),
ContentLength: aws.Int64(info.ContentLength),
}
// 2. 组件预签名请求,获取预签名,默认 300s 过期
req, _ := Client.PutObjectRequest(&input)
if ttl == 0 {
ttl = 300
}
url, _, err = req.PresignRequest(time.Duration(ttl) * time.Second)
return
}
二、前端上传代码示例
<template>
<div class="content">
<a href="javascript:void(0)" @click="upload">
上传
</a>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: "putPreSign",
methods: {
upload: function(event) {
// 获取预签名地址信息
var url = "https://test.s3.cn-northwest-1.amazonaws.com.cn/xxx"
// 预签名地址跨域处理
url = url.replace("https://test.s3.cn-northwest-1.amazonaws.com.cn/","/test/")
// 获取文件数据
var file = event.target.files[0]
// 组装请求头 / 主要看 X-Amz-SignedHeaders 参与预签名的参数有哪些, 另外Content-Length 会通过浏览器自动识别文件大小加入请求头
header := {
headers: {
'Content-Type': this.file.type,
'X-Amz-Acl': 'public-read',
'X-Amz-Server-Side-Encryption': "AES256"
}
}
// 执行请求,注意,必须用 PUT 方式
axios.put(url, file, header).then(res => {
console.log(res)
}).catch( err => {
console.log(err)
})
}
}
</script>
vue3.0 下,修改根目录下的 vue.config.js 配置信息,添加跨域处理:
proxy: {
'/test/*': {
target: 'http://test.s3.cn-northwest-1.amazonaws.com.cn/',
changeOrigin: true,
pathRewrite: {
'^/test': '/'
}
}
}
常见错误:
403: 验签失败,要检查 X-Amz-SignedHeaders 包含了哪些参数签名,对应检查请求头是否存在对应的信息;400:如果文件 MD5 参与验签,头部 content-md5 参数错误,可以不加入文件 MD5 参与预签名;
404:bucket 存储桶不存在。
上传 HTTP 响应 200,则说明上传成功。
以上,祝好运!