Function Compute permet de configurer l'authentification par signature pour les déclencheurs HTTP. Lorsque vous activez cette authentification sur un nom de domaine personnalisé, API Gateway valide la signature de chaque requête entrante avant de la transmettre à votre fonction. Cette délégation de l'authentification vous permet de vous concentrer sur la logique métier.
Activez l'authentification par signature si vous souhaitez que seuls les appelants disposant d'une AccessKey valide puissent invoquer votre fonction via un nom de domaine personnalisé. Laissez cette option désactivée pour les endpoints publics ne nécessitant pas d'authentification.
Prérequis
Avant de commencer, assurez-vous d'avoir :
Activer l'authentification par signature
Connectez-vous à la console Function Compute. Dans le volet de navigation de gauche, choisissez Function Management > Custom Domain Name.
Dans la barre de navigation supérieure, sélectionnez la région où se trouve le nom de domaine personnalisé. Sur la page Custom Domains, cliquez sur le nom de domaine personnalisé concerné.
Dans le coin supérieur droit, cliquez sur Modify. Dans la section Authentication Settings, définissez Authentication Method sur Signature Authentication, puis cliquez sur Save.

Vérifier la configuration
Une fois l'authentification par signature activée, écrivez du code sur votre machine locale et exécutez-le afin d'envoyer une requête signée et de confirmer qu'API Gateway l'accepte. L'exemple suivant utilise Go pour construire et envoyer une requête signée.
Le projet d'exemple contient deux fichiers :
| Fichier | Objectif |
|---|---|
signature.go |
Implémente l'algorithme de signature |
main.go |
Envoie une requête POST signée à l'endpoint de votre fonction |

Fonctionnement de l'algorithme de signature
L'algorithme de signature construit l'en-tête Authorization en quatre étapes :
Construction de la chaîne canonique — Concaténez la méthode HTTP ainsi que les en-têtes
Accept,Content-MD5,Content-TypeetDate. Ajoutez ensuite les en-têtesx-acs-*triés (CanonicalizedOSSHeaders), puis la ressource canonisée (chemin URL et paramètres de requête triés).Calcul de la signature HMAC-SHA1 — Appliquez HMAC-SHA1 en utilisant votre AccessKey secret comme clé, puis encodez le résultat en Base64.
Formatage de la chaîne d'autorisation — Combinez le résultat sous la forme
acs <AccessKey ID>:<signature>.Ajout de l'en-tête — Définissez
Authorization: acs <AccessKey ID>:<signature>sur la requête.
signature.go
package sign
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"hash"
"io"
"net/http"
"sort"
"strings"
)
// GetPOPAuthStr returns the Authorization header value for a request.
func GetPOPAuthStr(accessKeyID string, accessKeySecret string, req *http.Request) string {
return "acs " + accessKeyID + ":" + GetPOPSignature(accessKeySecret, req)
}
// GetPOPSignature computes the signature for a request.
func GetPOPSignature(akSecret string, req *http.Request) string {
stringToSign := getStringToSign(req)
return GetROASignature(stringToSign, akSecret)
}
// GetROASignature applies HMAC-SHA1 and Base64-encodes the result.
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 builds the canonical string from the request.
func getStringToSign(req *http.Request) string {
queryParams := make(map[string]string)
for k, v := range req.URL.Query() {
queryParams[k] = v[0]
}
// Sort query parameters 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 x-acs-* headers alphabetically
hs.Sort()
// Build the canonicalized x-acs-* headers string
canonicalizedOSSHeaders := ""
for i := range hs.Keys {
canonicalizedOSSHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n"
}
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 sorts header keys and values together.
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
}
func (hs *Sorter) Sort() {
sort.Sort(hs)
}
func (hs *Sorter) Len() int {
return len(hs.Vals)
}
func (hs *Sorter) Less(i, j int) bool {
return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0
}
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
L'exemple suivant lit les identifiants depuis les variables d'environnement et envoie une requête POST signée à l'endpoint de votre fonction.
Cet exemple utilise des identifiants AccessKey permanents pour plus de simplicité. Pour les charges de travail en production, privilégiez Security Token Service (STS). Consultez Créer une AccessKey et Gérer les identifiants d'accès pour plus de détails.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"time"
"auth.fc.aliyun.com/sign"
)
func main() {
// Replace with your custom domain name or HTTP trigger endpoint.
url := "A custom domain name or the endpoint of the HTTP trigger"
// Read credentials from environment variables.
// Make sure ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET are set.
ak := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
sk := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
// Build the request body.
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 the request.
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")
// Sign the request and attach the Authorization header.
addAuthInfo(request, ak, sk)
// 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()
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))
}
if req.URL.Path == "" {
req.URL.Path = "/"
}
authHeader := sign.GetPOPAuthStr(ak, sk, req)
req.Header.Set("Authorization", authHeader)
}
Une réponse réussie ressemble à ceci :
Response Status: 200 OK
Response Body: Hello World!
Résoudre les erreurs courantes
« Required HTTP header Date was not specified »
Cause : La requête ne contient pas l'en-tête Date, l'en-tête Authorization, ou aucun des deux.
Solution :
Si la requête ne possède pas d'en-tête
Authorization, ajoutez la logique de signature en utilisant le code d'exemple ci-dessus.Si l'en-tête
Authorizationest présent mais queDateest absent, ajoutez un en-têteDatecorrespondant à l'heure UTC actuelle au format RFC 7231, par exempleThu, 04 Jan 2024 01:33:13 GMT. La fonctionaddAuthInfodansmain.goconfigure cela automatiquement.
« The difference between the request time and the current time is too large »
Cause : La signature a expiré. Signez à nouveau la requête en utilisant l'heure système actuelle.
« The request signature we calculated does not match the signature you provided »
Cause : La signature contenue dans la requête ne correspond pas à celle calculée par Function Compute à partir des mêmes entrées.
Solution : Vérifiez les points suivants dans l'ordre :
Identifiants — Assurez-vous que l'AccessKey ID et l'AccessKey secret présents dans vos variables d'environnement sont corrects.
Ordre de la chaîne canonique — Comparez votre implémentation étape par étape avec la fonction
getStringToSign. L'ordre requis est : méthode HTTP →Accept→Content-MD5→Content-Type→Date→ en-têtesx-acs-*triés → ressource canonisée.Format d'autorisation — Confirmez que la valeur de l'en-tête
Authorizationrespecte le formatacs <AccessKey ID>:<signature>. Vérifiez la sortie deGetPOPAuthStr.