JavaScript 结合 Go 实现 临时密钥(STS)

2023-08-15 13:41:23 浏览数 (4)

背景介绍

最近有个项目,需要通过网页上传文件到对象存储中,在查看COS快速入门时,文档建议使用获取临时密钥:

由于固定密钥放在前端会有安全风险,正式部署时我们推荐使用临时密钥的方式,实现过程为:前端首先请求服务端,服务端使用固定密钥调用 STS 服务申请临时密钥(具体内容请参见 临时密钥生成和使用指引 文档),然后返回临时密钥到前端使用。

没想到这个过程一言难尽啊。

开箱即用

先贴代码,以备后用,注意:这里的代码仅适合JavaScript和Go配合,特别是前端代码,和官网例子也是有区别的

后端采用gin框架,这里假设绑定到URL地址为/api/stsr.POST("/sts", tencentSTS)

这段代码授予了临时密钥所有的权限,实际使用时,建议按照最小权限原则进行授权,详细权限可以参考COS API 授权策略使用指引。

代码语言:go复制
package api

import (
	"github.com/gin-gonic/gin"
	"github.com/tencentyun/qcloud-cos-sts-sdk/go"
	"strings"
	"time"
)

type STSRequest struct {
	Region string
	Bucket string
}

func tencentSTS(c *gin.Context) {

	var request STSRequest
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(500, err)
		return
	}
	// 云 API 密钥 SecretId 建议通过环境变量或者本地文件来读取
	secretId := "<SecretId>"
	// 云 API 密钥 SecretKey 建议通过环境变量或者本地文件来读取
	secretKey := "<SecretKey>"
	appid := request.Bucket[strings.LastIndex(request.Bucket, "-") 1:]
	bucket := request.Bucket
	region := request.Region
	client := sts.NewClient(secretId, secretKey, nil)
	// 策略概述 https://cloud.tencent.com/document/product/436/18023
	opt := &sts.CredentialOptions{
		DurationSeconds: int64(time.Hour.Seconds()),
		Region:          "ap-guangzhou",
		Policy: &sts.CredentialPolicy{
			Statement: []sts.CredentialPolicyStatement{
				{
					Action: []string{
						// 所有权限
						"*",
					},
					Effect: "allow",
					Resource: []string{
						//这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径,例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
						//存储桶的命名格式为 BucketName-APPID,此处填写的 bucket 必须为此格式
						"qcs::cos:"   region   ":uid/"   appid   ":"   bucket   "/*",
					},
				},
			},
		},
	}
	if res, err := client.GetCredential(opt); err != nil {
		c.JSON(500, err)
	} else {
		c.JSON(200, res)
	}
}
代码语言:typescript复制
const cos = new COS({
  getAuthorization: function (options, callback) {
    // 异步获取临时密钥
    // 服务端 JS 和 PHP 例子:https://github.com/tencentyun/cos-js-sdk-v5/blob/master/server/
    // 服务端其他语言参考 COS STS SDK :https://github.com/tencentyun/qcloud-cos-sts-sdk
    // STS 详细文档指引看:https://cloud.tencent.com/document/product/436/14048
    const url = '/api/sts' // url 替换成您自己的后端服务
    const xhr = new XMLHttpRequest()
    xhr.open('POST', url, true)
    xhr.onload = function () {
      try {
        const data = JSON.parse(this.responseText)
        const credentials = data.Credentials
        if (!data || !credentials) {
          return console.error('credentials invalid:n'   JSON.stringify(data, null, 2))
        }
        callback({
          TmpSecretId: credentials.TmpSecretId,
          TmpSecretKey: credentials.TmpSecretKey,
          SecurityToken: credentials.Token,
          // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
          StartTime: data.StartTime, // 时间戳,单位秒,如:1580000000
          ExpiredTime: data.ExpiredTime // 时间戳,单位秒,如:1580000000
        })
      } catch (e) {
        console.error('credentials invalid:'   e)
      }
    }
    xhr.send(JSON.stringify(options))
  }
})

开始吐槽

接下来是吐槽时间:

Go SDK中对CredentialResultCredentials的定义如下

代码语言:go复制
type Credentials struct {
	TmpSecretID  string `json:"TmpSecretId,omitempty"`
	TmpSecretKey string `json:"TmpSecretKey,omitempty"`
	SessionToken string `json:"Token,omitempty"`
}

type CredentialResult struct {
	Credentials *Credentials     `json:"Credentials,omitempty"`
	ExpiredTime int              `json:"ExpiredTime,omitempty"`
	Expiration  string           `json:"Expiration,omitempty"`
	StartTime   int              `json:"StartTime,omitempty"`
	RequestId   string           `json:"RequestId,omitempty"`
	Error       *CredentialError `json:"Error,omitempty"`
}

官网JavaScript代码如下:

代码语言:javascript复制
callback({
      TmpSecretId: credentials.tmpSecretId,
      TmpSecretKey: credentials.tmpSecretKey,
      SecurityToken: credentials.sessionToken,
      // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
      StartTime: data.startTime, // 时间戳,单位秒,如:1580000000
      ExpiredTime: data.expiredTime, // 时间戳,单位秒,如:1580000000
});

这里面tmpSecretIdtmpSecretKey等等所有的字段都是小写开头的,但是Go SDK中定义却是大写开头的,更坑的是,sessionToken这个字段在Go里面直接变成了Token。所以前文提供的javascript代码都修复了这些问题。

另外,文档中建议按照最小权限原则进行授权,但是COS API 授权策略使用指引居然没有列出所有的权限,搞得我干脆给了所有权限。

0 人点赞