All Products
Search
Document Center

Function Compute:Configure signature authentication for custom domain names

Last Updated:Sep 02, 2024

Function Compute allows you to configure signature authentication for HTTP triggers. When API Gateway receives requests from API operations that use Function Compute as the backend service, API Gateway authenticates the requests on custom domain names whose signature authentication feature is enabled. This way, your function does not need to authenticate request signatures and you can focus only on business logic. This topic describes how to configure signature authentication for custom domain names in the Function Compute console.

Before you start

Create a custom domain name

Procedure

  1. Log on to the Function Compute console. In the left-side navigation pane, choose Advanced Features > Custom Domains.

  2. In the top navigation bar, select the region where the custom domain name that you want to manage resides. On the Custom Domains page, click the custom domain name that you want to manage.

  3. In the upper-right corner of the page that appears, Click Modify. In the Authentication Settings section, set Authentication Method to Signature Authentication and click Save.

    image.png

Verification

Write code on your on-premises machine and run the code. The following figure shows the code structure.

image

Sample code:

  • 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

    The following sample code uses environment variables to obtain an AccessKey pair for invocation. This method is for reference only. We recommend that you use Security Token Service (STS), which is more secure. For more information, see Create an AccessKey and Manage access credential.

    package main
    
    import (
    	"bytes"
    	"encoding/json"
    	"fmt"
    	"io/ioutil"
    	"net/http"
    	"os"
    	"time"
    
    	"auth.fc.aliyun.com/sign"
    )
    
    func main() {
    	// The request URL. Specify the URL based on your business requirements.
    	url := "A custom domain name or the endpoint of the HTTP trigger"
     
            // In this example, the AccessKey ID and AccessKey secret are stored in environment variables to implement identity authentication. 
      // Make sure that the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables are configured. 
    	// If the project code is leaked, the AccessKey pair may be disclosed, which may compromise the security of resources in your account. 
    	ak := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    	sk := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
      
    	// Specify the data that you want to send.
    	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
    	}
    
    	// Create a sample request.
    	request, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
    	if err != nil {
    		fmt.Printf("Error creating request: %s\n", err)
    		return
    	}
    
    	// Add a request header.
    	request.Header.Set("Content-Type", "application/json")
    
    	addAuthInfo(request, ak, sk)
    
    	// Create an HTTP client and send the request.
    	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()
    
    	// Read the content of the response that is returned.
    	body, err := ioutil.ReadAll(response.Body)
    	if err != nil {
    		fmt.Printf("Error reading response body: %s\n", err)
    		return
    	}
    
    	// Print the content of the response.
    	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)
    }

The following command output indicates that the response of the function is obtained.

Response Status: 200 OK
Response Body: Hello World!

FAQ

Why is "required HTTP header Date was not specified" returned when I use a custom domain name to access functions after I enable signature authentication for the domain name?

The message indicates that the authentication failed. The following items list the possible causes:

  1. The request does not contain a signature.

  2. The request contains a signature but does not contain the Date header.

Why is "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" returned when I use a custom domain name to access functions after I enable signature authentication for the domain name?

The message indicates that the signature has expired. Use the current point in time to re-sign the request.

Why is "The request signature we calculated does not match the signature you provided. Check your access key and signing method" returned when I use a custom domain name to access functions after I enable signature authentication for the domain name?

The authentication fails because the signature in the request is inconsistent with the signature calculated by Function Compute.