You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
101 lines
2.6 KiB
101 lines
2.6 KiB
package common
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// PresignOSS 生成 S3 兼容的预签名 URL(MinIO / 阿里云 OSS),method 为 HTTP 方法(GET/PUT)
|
|
// objectKey 为对象 key(不含 bucket),expires 为有效期秒数。
|
|
func PresignOSS(method, objectKey string, expires int64) (string, error) {
|
|
oss := AppConf.OSS
|
|
if oss.Endpoint == "" {
|
|
return "", NewBusiError(InternalError, "OSS 未配置")
|
|
}
|
|
u, err := url.Parse(oss.Endpoint)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
host := u.Host
|
|
scheme := u.Scheme
|
|
if scheme == "" {
|
|
scheme = "https"
|
|
}
|
|
region := oss.Region
|
|
if region == "" {
|
|
region = "us-east-1"
|
|
}
|
|
const service = "s3"
|
|
|
|
now := time.Now().UTC()
|
|
amzDate := now.Format("20060102T150405Z")
|
|
dateStamp := now.Format("20060102")
|
|
credentialScope := dateStamp + "/" + region + "/" + service + "/aws4_request"
|
|
|
|
canonicalURI := encodeObjectPath(oss.Bucket + "/" + objectKey)
|
|
|
|
query := url.Values{}
|
|
query.Set("X-Amz-Algorithm", "AWS4-HMAC-SHA256")
|
|
query.Set("X-Amz-Credential", oss.AccessKey+"/"+credentialScope)
|
|
query.Set("X-Amz-Date", amzDate)
|
|
query.Set("X-Amz-Expires", strconv.FormatInt(expires, 10))
|
|
query.Set("X-Amz-SignedHeaders", "host")
|
|
|
|
canonicalHeaders := "host:" + host + "\n"
|
|
const signedHeaders = "host"
|
|
const payloadHash = "UNSIGNED-PAYLOAD"
|
|
|
|
canonicalRequest := strings.Join([]string{
|
|
method,
|
|
canonicalURI,
|
|
query.Encode(),
|
|
canonicalHeaders,
|
|
signedHeaders,
|
|
payloadHash,
|
|
}, "\n")
|
|
|
|
stringToSign := strings.Join([]string{
|
|
"AWS4-HMAC-SHA256",
|
|
amzDate,
|
|
credentialScope,
|
|
sha256Hex([]byte(canonicalRequest)),
|
|
}, "\n")
|
|
|
|
signingKey := deriveSigningKey(oss.SecretKey, dateStamp, region, service)
|
|
signature := hex.EncodeToString(hmacSHA256(signingKey, []byte(stringToSign)))
|
|
|
|
query.Set("X-Amz-Signature", signature)
|
|
return scheme + "://" + host + canonicalURI + "?" + query.Encode(), nil
|
|
}
|
|
|
|
// encodeObjectPath 对对象路径做分段 URL 编码(保留 "/" 分隔符),并以 "/" 开头
|
|
func encodeObjectPath(key string) string {
|
|
segs := strings.Split(key, "/")
|
|
for i, s := range segs {
|
|
segs[i] = url.PathEscape(s)
|
|
}
|
|
return "/" + strings.Join(segs, "/")
|
|
}
|
|
|
|
func sha256Hex(data []byte) string {
|
|
sum := sha256.Sum256(data)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func hmacSHA256(key, data []byte) []byte {
|
|
h := hmac.New(sha256.New, key)
|
|
h.Write(data)
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
func deriveSigningKey(secret, date, region, service string) []byte {
|
|
kDate := hmacSHA256([]byte("AWS4"+secret), []byte(date))
|
|
kRegion := hmacSHA256(kDate, []byte(region))
|
|
kService := hmacSHA256(kRegion, []byte(service))
|
|
return hmacSHA256(kService, []byte("aws4_request"))
|
|
}
|
|
|