全部產品
Search
文件中心

Function Compute:為自訂網域名配置簽名認證

更新時間:Aug 06, 2024

Function Compute支援為自訂配置簽名認證,當請求訊息到達Function Compute網關後,網關會對開啟簽名認證的自訂網域名上的請求進行認證,您的函數無需再次對請求籤名進行認證,只需關注商務邏輯即可。本文介紹如何通過控制台為自訂網域名配置簽名認證。

前提條件

建立自訂網域名

操作步驟

  1. 登入Function Compute控制台,在左側導覽列,選擇進階功能 > 網域名稱管理

  2. 在頂部功能表列,選擇地區,然後在網域名稱管理頁面,單擊目標網域名稱。

  3. 單擊右上方的編輯,在編輯自訂網域名頁面的認證設定地區,認證方式設定為簽名認證,然後單擊儲存

    image.png

結果驗證

在本地編寫代碼並執行代碼,代碼目錄結構如下。

image

程式碼範例如下。

  • 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?

該提示說明認證失敗,可能原因如下:

  1. 沒有在請求中進行簽名。

  2. 在請求中做了簽名,但是沒有提供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計算得到的簽名不一致,認證失敗。