最近更新时间:2024-03-19 19:20:44
使用KS3时,所有发送的非匿名请求都需要携带签名,KS3服务器端收到消息后,进行身份验证,验证成功则接受并执行请求,否则将会返回403错误信息并丢弃此请求。本文主要介绍V2签名算法在Go语言中的实现方式,并且通过Authorization Header来携带签名信息。
本部分代码介绍如何在Go语言中计算请求签名,KS3主要提供了两个函数。
如下所示为该函数对应的参数信息:
参数名称 | 描述 |
---|---|
ak | 您的AccessKeyID,必填。 |
sk | 您的SecretAccessKey,必填。 |
bucket | 访问的存储空间名称,如:test-bucket。若您想访问全部存储空间,则此处传空字符。 |
objectKey | 访问的对象,如:demo.txt。若您只想针对存储空间进行操作,则此处传空字符。 |
subResource | 子资源,如:acl,policy等。若没有则此处传空字符。 |
req | 请求对象,必填。 |
如下所示为该函数对应的返回值信息:
返回值名称 | 描述 |
---|---|
Authorization | 构造出的Authorization请求头的值。 |
如下所示为该函数对应的参数信息:
参数名称 | 描述 |
---|---|
endpoint | 访问域名,如:ks3-cn-beijing.ksyuncs.com,必填。 |
bucket | 访问的存储空间名称,如:test-bucket。若您想访问全部存储空间,则此处传空字符。 |
objectKey | 访问的对象,如:demo.txt。若若您只想针对存储空间进行操作,则此处传空字符。 |
subResource | 子资源,如:acl,policy等。若没有则此处传空字符。 |
query | 查询参数,如:prefix=test&max-keys=100。若没有则此处传空字符。 |
如下所示为该函数对应的返回值信息:
返回值名称 | 描述 |
---|---|
URL | 构造出的请求URL值。 |
如下所示为详细代码示例:
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"hash"
"io"
"net/http"
"net/url"
"sort"
"strings"
)
// SignV2 构造V2请求签名
// ak 您的AccessKeyID,必填
// sk 您的SecretAccessKey,必填
// bucket 访问的存储空间名称,如:test-bucket。若您想ListBuckets,则此处传空字符
// objectKey 访问的对象,如:demo.txt。若您是针对bucket进行的操作,则此处传空字符
// subResource 子资源,如:acl,policy等。若没有则此处传空字符
// req 请求对象,必填
func SignV2(ak string, sk string, bucket string, objectKey string, subResource string, req *http.Request) string {
if ak == "" || sk == "" {
return ""
}
// 获取规范化请求资源
canonicalizedResource := getCanonicalizedResource(bucket, objectKey, subResource)
// 获取规范化x-kss-请求头
canonicalizedKssHeaders := getCanonicalizedKssHeaders(req)
// 获取待签名字符串
stringToSign := getStringToSign(req, canonicalizedResource, canonicalizedKssHeaders)
// 获取签名
signature := getSignature(stringToSign, sk)
// 构造Authorization请求头
Authorization := "KSS " + ak + ":" + signature
return Authorization
}
func getCanonicalizedResource(bucketName, objectName, subResource string) string {
if subResource != "" {
subResource = "?" + subResource
}
if bucketName == "" {
return fmt.Sprintf("/%s%s", bucketName, subResource)
}
objectName = encodeKS3Str(objectName)
resource := "/" + bucketName + "/" + objectName + subResource
return resource
}
func getCanonicalizedKssHeaders(req *http.Request) string {
ks3HeadersMap := make(map[string]string)
for k, v := range req.Header {
if strings.HasPrefix(strings.ToLower(k), "x-kss-") {
ks3HeadersMap[strings.ToLower(k)] = v[0]
}
}
hs := newHeaderSorter(ks3HeadersMap)
hs.Sort()
canonicalizedKssHeaders := ""
for i := range hs.Keys {
canonicalizedKssHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n"
}
return canonicalizedKssHeaders
}
func getStringToSign(req *http.Request, canonicalizedResource string, canonicalizedKssHeaders string) string {
date := req.Header.Get("Date")
contentType := req.Header.Get("Content-Type")
contentMd5 := req.Header.Get("Content-MD5")
stringToSign := req.Method + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedKssHeaders + canonicalizedResource
return stringToSign
}
func getSignature(stringToSign string, sk string) string {
h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(sk))
io.WriteString(h, stringToSign)
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
return signature
}
func encodeKS3Str(str string) string {
objectName := url.QueryEscape(str)
objectName = strings.ReplaceAll(objectName, "+", "%20")
objectName = strings.ReplaceAll(objectName, "*", "%2A")
objectName = strings.ReplaceAll(objectName, "%7E", "~")
objectName = strings.ReplaceAll(objectName, "%2F", "/")
if strings.HasPrefix(objectName, "/") {
objectName = strings.Replace(objectName, "/", "%2F", 1)
}
objectName = strings.ReplaceAll(objectName, "//", "/%2F")
return objectName
}
func newHeaderSorter(m map[string]string) *HeaderSorter {
hs := &HeaderSorter{
Keys: make([]string, 0, len(m)),
Vals: make([]string, 0, len(m)),
}
for k, v := range m {
hs.Keys = append(hs.Keys, k)
hs.Vals = append(hs.Vals, v)
}
return hs
}
type HeaderSorter struct {
Keys []string
Vals []string
}
func (hs *HeaderSorter) Sort() {
sort.Sort(hs)
}
func (hs *HeaderSorter) Len() int {
return len(hs.Vals)
}
func (hs *HeaderSorter) Less(i, j int) bool {
return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0
}
func (hs *HeaderSorter) Swap(i, j int) {
hs.Vals[i], hs.Vals[j] = hs.Vals[j], hs.Vals[i]
hs.Keys[i], hs.Keys[j] = hs.Keys[j], hs.Keys[i]
}
// GetUrl 构造请求的URL
// endpoint 访问域名,如:ks3-cn-beijing.ksyuncs.com,必填
// bucket 访问的存储空间名称,如:test-bucket。若您想ListBuckets,则此处传空字符
// objectKey 访问的对象,如:demo.txt。若您是针对bucket进行的操作,则此处传空字符
// subResource 子资源,如:acl,policy等。若没有则此处传空字符
// query 查询参数,如:prefix=test&max-keys=100。若没有则此处传空字符
func GetUrl(endpoint string, bucket string, objectKey string, subResource string, query string) string {
queryString := ""
if subResource != "" {
queryString = "?" + subResource
}
if query != "" {
queryString = "?" + query
}
if subResource != "" && query != "" {
queryString = "?" + subResource + "&" + query
}
resource := encodeKS3Str(objectKey) + queryString
if bucket == "" {
return fmt.Sprintf("https://%s/%s", endpoint, resource)
}
return fmt.Sprintf("https://%s.%s/%s", bucket, endpoint, resource)
}
使用签名的主要步骤如下:
填写AK,SK等信息
构造请求URL
构造HTTP请求
计算签名
添加Authorization请求头
发送请求
以下为使用签名的详细代码示例:
package main
import (
"fmt"
"net/http"
"strings"
"time"
)
func main() {
// 填写AK
ak := "AccessKeyID"
// 填写SK
sk := "SecretAccessKey"
// 填写访问域名
endpoint := "ks3-cn-beijing.ksyuncs.com"
// 填写bucket的名称
bucket := "bucketName"
fmt.Println("---------------- 上传对象 ----------------")
putObject(ak, sk, endpoint, bucket)
fmt.Println("---------------- 获取对象 ----------------")
getObject(ak, sk, endpoint, bucket)
fmt.Println("---------------- 获取对象的ACL ----------------")
getObjectAcl(ak, sk, endpoint, bucket)
fmt.Println("---------------- 删除对象 ----------------")
deleteObject(ak, sk, endpoint, bucket)
fmt.Println("---------------- 列举bucket ----------------")
listBucket(ak, sk, endpoint)
fmt.Println("---------------- 列举object ----------------")
listObject(ak, sk, endpoint, bucket)
}
func putObject(ak string, sk string, endpoint string, bucket string) {
objectKey := "demo.txt"
// 构造请求URL
url := GetUrl(endpoint, bucket, objectKey, "", "")
fmt.Println("requestURL: ", url)
// 构造HTTP请求
req, err := http.NewRequest("PUT", url, strings.NewReader("test content"))
if err != nil {
panic(err)
}
// Date 表示此次请求操作的时间,必须为 HTTP1.1 中支持的 GMT 格式,例如:Tue, 30 Nov 2021 06:29:38 GMT。
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
req.Header.Add("Content-Type", "text/plain")
req.Header.Add("x-kss-acl", "public-read")
// 计算签名
Authorization := SignV2(ak, sk, bucket, objectKey, "", req)
fmt.Println("Authorization: ", Authorization)
// 添加Authorization请求头
req.Header.Set("Authorization", Authorization)
// 发送请求
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Println("StatusCode: ", resp.StatusCode)
fmt.Println("Status: ", resp.Status)
}
func getObject(ak string, sk string, endpoint string, bucket string) {
objectKey := "demo.txt"
// 构造请求URL
url := GetUrl(endpoint, bucket, objectKey, "", "")
fmt.Println("requestURL: ", url)
// 构造HTTP请求
req, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
}
// Date 表示此次请求操作的时间,必须为 HTTP1.1 中支持的 GMT 格式,例如:Tue, 30 Nov 2021 06:29:38 GMT。
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
// 计算签名
Authorization := SignV2(ak, sk, bucket, objectKey, "", req)
fmt.Println("Authorization: ", Authorization)
// 添加Authorization请求头
req.Header.Set("Authorization", Authorization)
// 发送请求
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Println("StatusCode: ", resp.StatusCode)
fmt.Println("Status: ", resp.Status)
}
func getObjectAcl(ak string, sk string, endpoint string, bucket string) {
objectKey := "demo.txt"
subResource := "acl"
// 构造请求URL
url := GetUrl(endpoint, bucket, objectKey, subResource, "")
fmt.Println("requestURL: ", url)
// 构造HTTP请求
req, err := http.NewRequest("GET", url, strings.NewReader("test content"))
if err != nil {
panic(err)
}
// Date 表示此次请求操作的时间,必须为 HTTP1.1 中支持的 GMT 格式,例如:Tue, 30 Nov 2021 06:29:38 GMT。
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
// 计算签名
Authorization := SignV2(ak, sk, bucket, objectKey, subResource, req)
fmt.Println("Authorization: ", Authorization)
// 添加Authorization请求头
req.Header.Set("Authorization", Authorization)
// 发送请求
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Println("StatusCode: ", resp.StatusCode)
fmt.Println("Status: ", resp.Status)
}
func deleteObject(ak string, sk string, endpoint string, bucket string) {
objectKey := "demo.txt"
// 构造请求URL
url := GetUrl(endpoint, bucket, objectKey, "", "")
fmt.Println("requestURL: ", url)
// 构造HTTP请求
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
panic(err)
}
// Date 表示此次请求操作的时间,必须为 HTTP1.1 中支持的 GMT 格式,例如:Tue, 30 Nov 2021 06:29:38 GMT。
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
// 计算签名
Authorization := SignV2(ak, sk, bucket, objectKey, "", req)
fmt.Println("Authorization: ", Authorization)
// 添加Authorization请求头
req.Header.Set("Authorization", Authorization)
// 发送请求
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Println("StatusCode: ", resp.StatusCode)
fmt.Println("Status: ", resp.Status)
}
func listBucket(ak string, sk string, endpoint string) {
// 构造请求URL
url := GetUrl(endpoint, "", "", "", "")
fmt.Println("requestURL: ", url)
// 构造HTTP请求
req, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
}
// Date 表示此次请求操作的时间,必须为 HTTP1.1 中支持的 GMT 格式,例如:Tue, 30 Nov 2021 06:29:38 GMT。
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
// 计算签名
Authorization := SignV2(ak, sk, "", "", "", req)
fmt.Println("Authorization: ", Authorization)
// 添加Authorization请求头
req.Header.Set("Authorization", Authorization)
// 发送请求
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Println("StatusCode: ", resp.StatusCode)
fmt.Println("Status: ", resp.Status)
}
func listObject(ak string, sk string, endpoint string, bucket string) {
query := "prefix=test&max-keys=100"
// 构造请求URL
url := GetUrl(endpoint, bucket, "", "", query)
fmt.Println("requestURL: ", url)
// 构造HTTP请求
req, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
}
// Date 表示此次请求操作的时间,必须为 HTTP1.1 中支持的 GMT 格式,例如:Tue, 30 Nov 2021 06:29:38 GMT。
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
// 计算签名
Authorization := SignV2(ak, sk, bucket, "", "", req)
fmt.Println("Authorization: ", Authorization)
// 添加Authorization请求头
req.Header.Set("Authorization", Authorization)
// 发送请求
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Println("StatusCode: ", resp.StatusCode)
fmt.Println("Status: ", resp.Status)
}
纯净模式