【爬坑指南】文件中心 AWS S3 预签名URL 前端直传

在这里插入图片描述

一、前言

开门见山的说,笔者已经被AWS坑的体无完肤了,文档难找、SDK版本繁多老版本没有注释、例子不全还有误导的情况、MD5-Hex不用一定要用MD5-Base64等等各种问题导致在使用的过程中各种卡壳,不过好在最终还是把问题解决,才有了今天给大家带来了【爬坑指南】,我们先从要做一件什么事情开始说起:

资源汇总:

二、文件中心?上传文件很难吗?

首先上传文件并不难,from-data谁还不会呢?但是要把文件传好传安全很难,考虑点会非常多:

  • 运维层面:
    • 用什么存储资源?
    • 使用了不同的云有差异怎么办?
    • CDN用哪家的?能不能授权?
    • 文件上传服务器无状态POD服务是否需要挂在共享磁盘
    • 跨域配置
  • 安全层面:
    • 谁传的文件谁访问,不能越权
    • 用户上传身份校验
    • 文件UGC和安全检查
    • 上传控制频率
    • 上传文件名随机化
    • 上传存储的的secret key安全保存2
    • 后端校验文件大小等后缀验证以及文件头信息逻辑
    • 文件一致性校验
  • 功能层面:
    • 缩略图 OCR
    • 断点续传 or 分片上传 or 文件秒传
    • 视频压缩格式转码
    • 未使用的文件自动清理,节省资源

在根据业务的不同还有更多的扩展性要求。

如果当业务遇到文件上传场景的时候把这一套做了一遍,然后另外一个业务也需要使用在重来一遍就非常头疼了,所以就诞生出做一个通用的文件上传的想法了。

三、为什么要用前端直传?

如果在遇到内部通讯协议使用Grpc或者是http-json为主,还需要对于网关鉴权进行改造和单独配置,会有非常多的额外成本,所以考虑使用前端直传的场景成本最低,而且因为各家云厂商提供的SDK成熟度高各项功能都有 (这里是第一个失策阿里云和腾讯云等都有非常完善的JS-SDK,没想到AWS提供的非常的简单)

并且前端也可以封装成一个标准SDK和后端对应,遇到文件上传场景抄起来就直接用就可以了。

整个流程大家可以参考下面这张流程图,整体分为五个阶段:

  • 前端获取上传凭证,服务端校验用户上传行为合法性后申请凭证返回
  • 前端只传文件到对应的对象存储
  • 完成上传上报服务端完成上传,服务端下载文件进行校验,没问题返回临时访问URL
  • 前端完成表单填写提交到业务服务端,业务服务端通过文件资源ID告知文件中心文件被使用,保存资源ID(如果是公开访问的文件文件中心可以直接返回长授权URL)
  • 用户访问资源时使用资源ID换取临时访问授权

在这里插入图片描述

四、AWS Go SDK 创建预签名URL × Postman模拟上传

首先我们需要确定需要使用AWS的哪些能力:

  • 上传文件校验MD5完整和文件长度 AWS进行一轮校验(可选),防止申请的预签名的文件和实际的不符合
  • 定义文件是否可以被公开访问 ACLPrivate
  • 授权时效 上传时效和被临时访问时效
package test

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/s3/types"
)

const AK = "XXXX"                     // AWS AK
const SK = "XXXX"                     // AWS SK
const BUCKET = "XXXX"                 // AWS BUCKET Name

type Credential struct {
	Ak string
	Sk string
}

func (c *Credential) Retrieve(ctx context.Context) (aws.Credentials, error) {
	return aws.Credentials{
		AccessKeyID: c.Ak, SecretAccessKey: c.Sk,
	}, nil
}

var client *s3.Client

func init() {
	cred := &Credential{
		Ak: AK,
		Sk: SK,
	}

	cfg := aws.Config{
		Region:                      "ap-southeast-1",
		Credentials:                 cred,
		BearerAuthTokenProvider:     nil,
		HTTPClient:                  nil,
		EndpointResolverWithOptions: nil,
		RetryMaxAttempts:            0,
		RetryMode:                   "",
		Retryer:                     nil,
		ConfigSources:               nil,
		APIOptions:                  nil,
		Logger:                      nil,
		ClientLogMode:               0,
		DefaultsMode:                "",
		RuntimeEnvironment:          aws.RuntimeEnvironment{},
	}

	client = s3.NewFromConfig(cfg)
}

func TestAWSV2PresignPutObject(t *testing.T) {

	fmt.Println("Create Presign client")
	presignClient := s3.NewPresignClient(client)

	filemd5 := "/SX1AAfPuVitH7ZK9bNg6Q==" // 前端上传文件给到 AWS-S3 时文件MD5校验(可选)
	fileLen := 5616111                    // 前端上传文件给到 AWS-S3 时文件大小校验(可选)
	filename := "/demo/test.jpg"          // 文件存储bucket的路径和名称

	presignResult, err := presignClient.PresignPutObject(context.TODO(), &s3.PutObjectInput{
		Bucket:        aws.String(BUCKET),
		Key:           aws.String(filename),
		ACL:           types.ObjectCannedACLPrivate,
		ContentMD5:    &filemd5,
		ContentLength: int64(fileLen),
	}, func(po *s3.PresignOptions) {
		// 授权时效
		po.Expires = 48 * time.Hour
	})

	if err != nil {
		panic("Couldn't get presigned URL for GetObject")
	}
	fmt.Printf("上传URL: %s\n", presignResult.URL)

	presignResult, err = presignClient.PresignGetObject(context.TODO(), &s3.GetObjectInput{
		Bucket: aws.String(BUCKET),
		Key:    aws.String(filename),
	}, func(po *s3.PresignOptions) {
		// 授权时效
		po.Expires = 48 * time.Hour
	})

	if err != nil {
		panic("Couldn't get presigned URL for GetObject")
	}
	fmt.Printf("访问URL: %s\n", presignResult.URL)

}

使用上述代码我们就能得到以下内容:

上传URL: https://xxxx.s3.ap-southeast-1.amazonaws.com/demo/test.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIASQSSOHDZ5AN5LXUF%2F20221021%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Date=20221021T135608Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=content-md5%3Bhost&x-id=PutObject&X-Amz-Signature=e23da84351a1afa7faaabfad533a26c8f2dbeb0b14fc4384560b40622726cbaa
访问URL: https://xxxx.s3.ap-southeast-1.amazonaws.com/demo/test.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIASQSSOHDZ5AN5LXUF%2F20221021%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Date=20221021T135608Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&x-id=GetObject&X-Amz-Signature=7af80761fd063ed554827b5ab3e7d00b35cf67d98221157bbb39d247865a152e

我们可以通过postman来调用进行文件上传,使用put方式,binary上传文件二进制
在这里插入图片描述
这里千万不能使用from-data 因为上传访问是把所有的body内容保存在文件中,一定要使用binary的方式,如果你使用文件大小校验和md5校验一定会提示校验不通过,因为文件内容和实际文件内容不一样,如下:

----------------------------642585057435781546021821
Content-Disposition: form-data; name=""; filename="test.txt"
Content-Type: text/plain

omori
----------------------------642585057435781546021821--

对于使用了长度限制和MD5校验的前端在请求过程中需要在Headers里面增加对应的配置,才能通过sign校验,因为服务端在生成签名的时候 md5和len也参与了运算,http状态码返回200,恭喜你已经完成了上传动作了
在这里插入图片描述

AWS-S3使用的MD5-Base64,我们场景的MD5 32位是16进制的表示方式,但要注意不是讲16进制的MD5进行base64,是需要对原始的byte数组的MD5进行Base64,比如js-md5:

md5 = require('js-md5');
console.log(md5.base64("test"))
console.log(md5('test'))
console.log(md5.array('test'))

CY9rzUYh03PK3k6DJie09g== <-这个是正确的
098f6bcd4621d373cade4e832627b4f6
[
    9, 143, 107, 205,  70,
   33, 211, 115, 202, 222,
   78, 131,  38,  39, 180,
  246
]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文振熙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值