Function Compute支援為自訂配置簽名認證,當請求訊息到達Function Compute網關後,網關會對開啟簽名認證的自訂網域名上的請求進行認證,您的函數無需再次對請求籤名進行認證,只需關注商務邏輯即可。本文介紹如何通過控制台為自訂網域名配置簽名認證。
前提條件
操作步驟
登入Function Compute控制台,在左側導覽列,選擇 。
在頂部功能表列,選擇地區,然後在網域名稱管理頁面,單擊目標網域名稱。
單擊右上方的編輯,在編輯自訂網域名頁面的認證設定地區,認證方式設定為簽名認證,然後單擊儲存。
結果驗證
在本地編寫代碼並執行代碼,代碼目錄結構如下。
程式碼範例如下。
signature.go
package sign import ( "bytes" "crypto/hmac" "crypto/sha1" "encoding/base64" "hash" "io" "net/http" "sort" "strings" ) // GetPOPAuthStr ... GetAuthStr get signature strings // // @param accessKeyID // @param accessKeySecret // @param req // @return string func GetPOPAuthStr(accessKeyID string, accessKeySecret string, req *http.Request) string { return "acs " + accessKeyID + ":" + GetPOPSignature(accessKeySecret, req) } // GetPOPSignature ... ... // // @param akSecret // @param req // @return string func GetPOPSignature(akSecret string, req *http.Request) string { stringToSign := getStringToSign(req) // fmt.Printf("stringToSign: %s\n", stringToSign) return GetROASignature(stringToSign, akSecret) } // GetROASignature ... ... // // @param stringToSign // @param secret // @return string func GetROASignature(stringToSign string, secret string) string { h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(secret)) io.WriteString(h, stringToSign) signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil)) return signedStr } // getStringToSign ... func getStringToSign(req *http.Request) string { queryParams := make(map[string]string) for k, v := range req.URL.Query() { queryParams[k] = v[0] } // sort QueryParams by key var queryKeys []string for key := range queryParams { queryKeys = append(queryKeys, key) } sort.Strings(queryKeys) tmp := "" for i := 0; i < len(queryKeys); i++ { queryKey := queryKeys[i] v := queryParams[queryKey] if v != "" { tmp = tmp + "&" + queryKey + "=" + v } else { tmp = tmp + "&" + queryKey } } resource := req.URL.EscapedPath() if tmp != "" { tmp = strings.TrimLeft(tmp, "&") resource = resource + "?" + tmp } return getSignedStr(req, resource) } func getSignedStr(req *http.Request, canonicalizedResource string) string { temp := make(map[string]string) for k, v := range req.Header { if strings.HasPrefix(strings.ToLower(k), "x-acs-") { temp[strings.ToLower(k)] = v[0] } } hs := newSorter(temp) // Sort the temp by the ascending order hs.Sort() // Get the canonicalizedOSSHeaders canonicalizedOSSHeaders := "" for i := range hs.Keys { canonicalizedOSSHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n" } // Give other parameters values // when sign URL, date is expires date := req.Header.Get("Date") accept := req.Header.Get("Accept") contentType := req.Header.Get("Content-Type") contentMd5 := req.Header.Get("Content-MD5") signStr := req.Method + "\n" + accept + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedOSSHeaders + canonicalizedResource return signStr } // Sorter ... type Sorter struct { Keys []string Vals []string } func newSorter(m map[string]string) *Sorter { hs := &Sorter{ 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 } // Sort is an additional function for function SignHeader. func (hs *Sorter) Sort() { sort.Sort(hs) } // Len is an additional function for function SignHeader. func (hs *Sorter) Len() int { return len(hs.Vals) } // Less is an additional function for function SignHeader. func (hs *Sorter) Less(i, j int) bool { return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0 } // Swap is an additional function for function SignHeader. func (hs *Sorter) 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] }
go.mod
module auth.fc.aliyun.com go 1.17
main.go
以下程式碼範例使用環境變數擷取AccessKey的方式進行調用,僅供參考,建議使用更安全的STS方式。更多資訊,請參見建立AccessKey和管理訪問憑證。
package main import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "os" "time" "auth.fc.aliyun.com/sign" ) func main() { // 請求的URL,請根據實際修改 url := "您的自訂網域名或者 HTTP 觸發器地址" // 本樣本將AccessKey ID和AccessKey Secret儲存在環境變數中實現身份認證為例。 // 請確保代碼運行環境設定了環境變數 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 // 工程代碼泄露可能會導致 AccessKey 泄露,並威脅帳號下所有資源的安全性。 ak := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID") sk := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET") // 建立要發送的資料 data := map[string]interface{}{ "user": "FC 3.0", } jsonData, err := json.Marshal(data) if err != nil { fmt.Printf("Error encoding JSON: %s\n", err) return } // 建立請求執行個體 request, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) if err != nil { fmt.Printf("Error creating request: %s\n", err) return } // 添加要求標頭資訊 request.Header.Set("Content-Type", "application/json") addAuthInfo(request, ak, sk) // 建立http.Client並發送請求 client := &http.Client{} response, err := client.Do(request) if err != nil { fmt.Printf("Error sending request to server: %s\n", err) return } defer response.Body.Close() // 讀取響應內容 body, err := ioutil.ReadAll(response.Body) if err != nil { fmt.Printf("Error reading response body: %s\n", err) return } // 列印響應內容 fmt.Printf("Response Status: %s\n", response.Status) fmt.Printf("Response Body: %s\n", string(body)) } func addAuthInfo(req *http.Request, ak, sk string) { if req.Header.Get("Date") == "" { req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat)) } // copy from lambda-go-sdk sign-url-request if req.URL.Path == "" { req.URL.Path = "/" } authHeader := sign.GetPOPAuthStr(ak, sk, req) req.Header.Set("Authorization", authHeader) }
執行成功後結果如下,表示已成功地擷取函數的Response。
Response Status: 200 OK
Response Body: Hello World!
常見問題
為什麼自訂網域名開啟簽名認證之後,通過自訂網域名訪問函數提示:required HTTP header Date was not specified?
該提示說明認證失敗,可能原因如下:
沒有在請求中進行簽名。
在請求中做了簽名,但是沒有提供Date這個Header。
為什麼自訂網域名開啟簽名認證之後,通過自訂網域名訪問函數提示:the difference between the request time 'Thu, 04 Jan 2024 01:33:13 GMT' and the current time 'Thu, 04 Jan 2024 08:34:58 GMT' is too large?
該提示說明簽名到期,請您重新使用目前時間進行簽名。
為什麼自訂網域名開啟簽名認證之後,通過自訂網域名訪問函數提示:The request signature we calculated does not match the signature you provided. Check your access key and signing method?
請求中的簽名與Function Compute計算得到的簽名不一致,認證失敗。