火山引擎对于每一次的 HTTPS 协议访问请求,会通过访问签名信息中的访问密钥(包括 Access Key ID 和 Secret Access Key),验证访问请求者身份。
账户和有权限的用户可以新建访问密钥,操作如下:
说明
CanonicalRequest = HTTPRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload))
在签名之前,要将请求正规化,使得签名计算过程无异议,其主要过程及伪代码如下:
字段 | 说明 |
---|---|
HTTPRequestMethod | HTTP 请求方法,例如 POST。 |
CanonicalURI | 正规化后的 URI。如果 URI 为空,那么使用/ 作为绝对路径。 |
CanonicalQueryString | 正规化后的 QueryString。QueryString 正规化流程如下:
|
CanonicalHeaders | 正规化后的Header。其中伪代码如下:
其中, 注意
|
SignedHeaders | 参与签名的 Header 名称。
|
RequestPayload | 完整的请求的 Body。
|
签名字符串主要包含请求以及正规化请求的元数据信息,由签名算法、请求日期、信任状和正规化请求哈希值连接组成,伪代码如下:
StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HexEncode(Hash(CanonicalRequest))
字段说明如下。
字段 | 说明 |
---|---|
Algorithm | 签名的算法。目前火山引擎仅支持 HMAC-SHA256 签名算法。 |
RequestDate | 请求 UTC 时间。请使用YYYYMMDD'T'HHMMSS'Z' 格式,例如:20201103T104027Z 。 |
CredentialScope | 凭证范围。格式为YYYYMMDD/region/service/request 。 |
CanonicalRequest | 前序正规化请求的结果。 |
kSecret = *Your Secret Access Key* kDate = HMAC(kSecret, Date) kRegion = HMAC(kDate, Region) kService = HMAC(kRegion, Service) kSigning = HMAC(kService, "request")
Signature = HexEncode(HMAC(kSigning, StringToSign))
说明
表达式中用{}
内的内容,代表上文计算出的中间过程。
Authorization: HMAC-SHA256 Credential={AccessKeyId}/{CredentialScope}, SignedHeaders={SignedHeaders}, Signature={Signature}
以下是签名机制的 Golang 实现,签名后,调用 veFaaS OpenAPI ListFunctions 接口。Python、Java、NodeJS、PHP 等更多语言实现的签名算法和示例,请参见 volcengine/volc-openapi-demos。
package main import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "log" "net/http" "net/http/httputil" "net/url" "strings" "time" ) const ( AccessKeyID = "AKxxx" SecretAccessKey = "yyyyyy" Addr = "https://iam.volcengineapi.com" Path = "/" // 路径,不包含 Query Service = "vefaas" Region = "cn-beijing" Action = "ListFunctions" Version = "2024-06-06" ) func hmacSHA256(key []byte, content string) []byte { mac := hmac.New(sha256.New, key) mac.Write([]byte(content)) return mac.Sum(nil) } func getSignedKey(secretKey, date, region, service string) []byte { kDate := hmacSHA256([]byte(secretKey), date) kRegion := hmacSHA256(kDate, region) kService := hmacSHA256(kRegion, service) kSigning := hmacSHA256(kService, "request") return kSigning } func hashSHA256(data []byte) []byte { hash := sha256.New() if _, err := hash.Write(data); err != nil { log.Printf("input hash err:%s", err.Error()) } return hash.Sum(nil) } func doRequest(method string, queries url.Values, body []byte) error { // 1. 构建请求 queries.Set("Action", Action) queries.Set("Version", Version) requestAddr := fmt.Sprintf("%s%s?%s", Addr, Path, queries.Encode()) log.Printf("request addr: %s\n", requestAddr) request, err := http.NewRequest(method, requestAddr, bytes.NewBuffer(body)) if err != nil { return fmt.Errorf("bad request: %w", err) } // 2. 构建签名材料 now := time.Now() date := now.UTC().Format("20060102T150405Z") authDate := date[:8] request.Header.Set("X-Date", date) payload := hex.EncodeToString(hashSHA256(body)) request.Header.Set("X-Content-Sha256", payload) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") queryString := strings.Replace(queries.Encode(), "+", "%20", -1) signedHeaders := []string{"host", "x-date", "x-content-sha256", "content-type"} var headerList []string for _, header := range signedHeaders { if header == "host" { headerList = append(headerList, header+":"+request.Host) } else { v := request.Header.Get(header) headerList = append(headerList, header+":"+strings.TrimSpace(v)) } } headerString := strings.Join(headerList, "\n") canonicalString := strings.Join([]string{ method, Path, queryString, headerString + "\n", strings.Join(signedHeaders, ";"), payload, }, "\n") log.Printf("canonical string:\n%s\n", canonicalString) hashedCanonicalString := hex.EncodeToString(hashSHA256([]byte(canonicalString))) log.Printf("hashed canonical string: %s\n", hashedCanonicalString) credentialScope := authDate + "/" + Region + "/" + Service + "/request" signString := strings.Join([]string{ "HMAC-SHA256", date, credentialScope, hashedCanonicalString, }, "\n") log.Printf("sign string:\n%s\n", signString) // 3. 构建认证请求头 signedKey := getSignedKey(SecretAccessKey, authDate, Region, Service) signature := hex.EncodeToString(hmacSHA256(signedKey, signString)) log.Printf("signature: %s\n", signature) authorization := "HMAC-SHA256" + " Credential=" + AccessKeyID + "/" + credentialScope + ", SignedHeaders=" + strings.Join(signedHeaders, ";") + ", Signature=" + signature request.Header.Set("Authorization", authorization) log.Printf("authorization: %s\n", authorization) // 4. 打印请求,发起请求 requestRaw, err := httputil.DumpRequest(request, true) if err != nil { return fmt.Errorf("dump request err: %w", err) } log.Printf("request:\n%s\n", string(requestRaw)) response, err := http.DefaultClient.Do(request) if err != nil { return fmt.Errorf("do request err: %w", err) } // 5. 打印响应 responseRaw, err := httputil.DumpResponse(response, true) if err != nil { return fmt.Errorf("dump response err: %w", err) } log.Printf("response:\n%s\n", string(responseRaw)) if response.StatusCode == 200 { log.Printf("请求成功") } else { log.Printf("请求失败") } return nil } func main() { // GET 请求例子 query1 := make(url.Values) doRequest(http.MethodGet, query1, nil) }