La méthode de signature V3 est le mécanisme utilisé par Alibaba Cloud API Gateway pour authentifier une requête HTTP ou HTTPS adressée à une opération d'API Alibaba Cloud. Signez vous-même vos requêtes lorsque vous ne souhaitez pas appeler les opérations d'API Alibaba Cloud à l'aide d'un kit de développement logiciel (SDK), ou lorsque votre environnement d'exécution ne prend pas en charge les SDK.
Notes d'utilisation
Migration depuis la V2 — Si vous utilisez actuellement la méthode de signature V2 pour appeler des opérations d'API, vous pouvez passer directement à la méthode de signature V3.
Couverture des SDK — OpenAPI Explorer fournit des SDK pour les services Alibaba Cloud, et les opérations d'API de ces services prennent en charge la méthode de signature V3.
Services avec des passerelles gérées en interne — Certains services Alibaba Cloud utilisent des passerelles gérées en interne, et leurs mécanismes d'authentification diffèrent du mécanisme décrit dans cette rubrique. Avant d'envoyer des requêtes HTTP à ces services, consultez la rubrique relative à la méthode de signature du service correspondant.
Simple Log Service (SLS) — Pour plus d'informations sur la méthode de signature de SLS, consultez Signatures de requête.
Object Storage Service (OSS) — Pour plus d'informations sur la méthode de signature d'OSS, consultez Méthodes de signature.
Syntaxe de la requête HTTP
Une requête d'API Alibaba Cloud complète se compose des éléments suivants.
| Nom | Obligatoire | Description | Exemple |
| Protocole | Oui | Vous pouvez consulter les références d'API de chaque service Alibaba Cloud pour obtenir les détails de configuration. Les requêtes peuvent être envoyées via le protocole HTTP ou HTTPS. Pour une sécurité accrue, nous vous recommandons d'envoyer les requêtes via HTTPS. Valeurs valides : https:// et http://. |
https:// |
| Endpoint | Oui | Le endpoint du service. Vous pouvez consulter la documentation des endpoints de chaque service Alibaba Cloud pour afficher les endpoints dans différentes régions. | ecs.cn-shanghai.aliyuncs.com |
| resource_URI_parameters | Oui | L'URI de la requête, incluant le chemin de l'API et les paramètres de requête qui résident dans le chemin ou la chaîne de requête de la requête. | ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai |
| RequestHeader | Oui | Les en-têtes de requête communs. Dans la plupart des cas, ils contiennent des informations telles que la version de l'API, Host et Authorization. Pour plus d'informations, consultez la section suivante. | Authorization: ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0 x-acs-action: RunInstances host: ecs.cn-shanghai.aliyuncs.com x-acs-date: 2023-10-26T10:22:32Z x-acs-version: 2014-05-26 x-acs-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 x-acs-signature-nonce: 3156853299f313e23d1673dc12e1703d |
| RequestBody | Oui | Les paramètres de requête métier définis dans le corps. Vous pouvez les obtenir à partir des métadonnées OpenAPI. | |
| HTTPMethod | Oui | La méthode de requête. Vous pouvez l'obtenir à partir des métadonnées OpenAPI. | POST |
RequestHeader
Lorsque vous appelez une opération d'API Alibaba Cloud, les en-têtes de requête communs doivent contenir les informations suivantes.
| Nom | Type | Obligatoire | Description | Exemple |
| host | String | Oui | Le endpoint du service. Pour plus d'informations, consultez . | ecs.cn-shanghai.aliyuncs.com |
| x-acs-action | String | Oui | Le nom de l'opération d'API. Accédez au Portail des développeurs OpenAPI d'Alibaba Cloud et recherchez l'opération d'API que vous souhaitez appeler. | RunInstances |
| x-acs-content-sha256 | String | Oui | Le résultat du hachage du corps de la requête, puis l'encodage de la valeur de hachage en Base16. La valeur est identique à celle de HashedRequestPayload. | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
| x-acs-date | String | Oui | L'heure UTC au format ISO 8601. Le format est yyyy-MM-ddTHH:mm:ssZ, par exemple, 2018-01-01T12:00:00Z. La valeur doit correspondre à une heure située dans les 15 minutes précédant l'envoi de la requête. | 2023-10-26T10:22:32Z |
| x-acs-signature-nonce | String | Oui | Le nonce de signature. Le nonce permet d'éviter les attaques par rejeu sur le réseau. Vous devez utiliser un nonce différent pour chaque requête. Ce mécanisme s'applique uniquement au protocole HTTP. | 3156853299f313e23d1673dc12e1703d |
| x-acs-version | String | Oui | Le numéro de version de l'API. Pour plus d'informations sur l'obtention du numéro de version, consultez Comment obtenir la version de l'API (x-acs-version) ?. | 2014-05-26 |
| Authorization | String | Requis pour les requêtes non anonymes | Les informations d'authentification utilisées pour vérifier la validité de la requête. Le format est Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature. SignatureAlgorithm est l'algorithme de chiffrement de la signature et doit être ACS3-HMAC-SHA256. Credential est l'ID AccessKey de l'utilisateur. Vous pouvez afficher votre ID AccessKey dans la console Resource Access Management (RAM). Pour créer une paire AccessKey, consultez Créer une paire AccessKey. SignedHeaders est la liste des noms d'en-têtes de requête utilisés pour le calcul de la signature. Remarque : Nous vous recommandons de calculer la signature en vous basant sur tous les en-têtes de requête communs, à l'exception de Authorization, afin d'améliorer la sécurité. Signature est la signature de la requête. Pour plus d'informations sur la valeur, consultez la section Méthode de signature. | ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0 |
| x-acs-security-token | String | Requis pour l'authentification STS | La valeur de SecurityToken dans la réponse de l'opération AssumeRole. |
Avant de calculer une signature
La méthode de signature V3 signe chaque requête avec une paire AccessKey (un ID AccessKey et un secret AccessKey). Les contraintes suivantes déterminent ce que vous signez et ce que vous envoyez. Examinez-les toutes avant d'écrire le code de signature.
Jeu de caractères — Les requêtes et les réponses sont encodées à l'aide du jeu de caractères UTF-8.
Fenêtre temporelle — La valeur de
x-acs-datedoit correspondre à une heure située dans les 15 minutes précédant l'envoi de la requête.Unicité du nonce — Utilisez une valeur
x-acs-signature-noncedifférente pour chaque requête. Le nonce permet d'éviter les attaques par rejeu sur le réseau. Ce mécanisme s'applique uniquement au protocole HTTP.Identifiants — Pour un identifiant à long terme, signez la requête avec votre paire AccessKey. Pour un identifiant temporaire émis par Security Token Service (STS), ajoutez également l'en-tête
x-acs-security-token, dont la valeur correspond à la valeur de SecurityToken dans la réponse de l'opération AssumeRole.Métadonnées de l'API — Avant de signer une requête, consultez les métadonnées OpenAPI pour obtenir la méthode de requête, les noms des paramètres de requête, les types de paramètres de requête et la manière de transmettre les paramètres. Sinon, la signature risque d'échouer.
APIs de style RPC et de style ROA
Le champ style dans les métadonnées de l'API détermine le CanonicalURI de la requête et les méthodes de requête prises en charge par l'API.
CanonicalURI — Pour une API de style RPC, le CanonicalURI est une barre oblique (
/). Pour une API de style ROA, le CanonicalURI est la valeur depathdans les métadonnées de l'API, par exemple/api/v1/clusters.Méthodes de requête — Une API de style RPC prend généralement en charge les requêtes GET et POST. Une API de style ROA ne prend en charge qu'une seule méthode de requête. Pour plus d'informations sur l'obtention des méthodes de requête prises en charge par une API, consultez les métadonnées OpenAPI.
**Autres valeurs de
style** — RPC et ROA n'affectent que la valeur de CanonicalURI. Sistyleest défini sur une valeur autre que RPC ou ROA, vérifiez si le paramètrepathpossède une valeur dans les informations récapitulatives des métadonnées de l'API. Si une valeur existe, définissez CanonicalURI sur la valeur depath. Si aucune valeur n'existe, définissez CanonicalURI sur une barre oblique (/). Dans les métadonnées d'API suivantes de l'opération qui interroge la liste des clusters ACK dans Container Service for Kubernetes, la valeur depathest/api/v1/clusters. Dans ce cas, la valeur de CanonicalURI est/api/v1/clusters.
{
"code": 0,
"data": {
"summary": "Queries the details about Container Service for Kubernetes (A...",
"path": "/api/v1/clusters",
"methods": [
"get"
],
"schemes": [
"http",
"https"
],
"security": [
{
"AK": []
}
]
}
}
Positions des paramètres
Dans les métadonnées de l'API, le champ in définit la position de chaque paramètre. La position détermine la manière dont le paramètre est transmis et le type de contenu requis par la requête.
| Position du paramètre | Description | content-type |
"in": "query" |
Paramètres de requête, qui apparaissent après le point d'interrogation (?) à la fin de l'URL de la requête. Les différentes paires name=value sont séparées par des esperluettes (&). |
Facultatif. Si vous le spécifiez, définissez la valeur sur application/json. |
"in": "formData" |
Paramètres de formulaire, qui doivent être concaténés en une chaîne au format key1=value1&key2=value2&key3=value3 et transmis dans le corps de la requête. De plus, si un paramètre de requête est de type array ou object, convertissez la value en paires clé-valeur indexées. Par exemple, la valeur object {"key":["value1","value2"]} doit être convertie en {"key.1":"value1","key.2":"value2"}. |
Requis. Définissez la valeur sur content-type=application/x-www-form-urlencoded. |
"in": "body" |
Paramètres de corps, qui sont transmis dans le corps de la requête. | Requis. La valeur de content-type dépend du type de contenu de la requête. Si le contenu de la requête est des données JSON, définissez content-type sur application/json. Si le contenu de la requête est au format binaire, tel qu'un flux de fichier binaire, définissez content-type sur application/octet-stream. |
Paramètres de tableau et d'objet
Si un paramètre de requête possède une structure de données complexe, convertissez la valeur du paramètre en paires clé-valeur indexées.
Exemple 1 : {"InstanceId":["i-bp10igfmnyttXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX"]} doit être converti en :
{
"InstanceId.1": "i-bp10igfmnyttXXXXXXXX",
"InstanceId.10": "i-bp10igfmnyttXXXXXXXX",
"InstanceId.11": "i-bp10igfmnyttXXXXXXXX",
"InstanceId.12": "i-bp10igfmnyttXXXXXXXX",
"InstanceId.2": "i-bp1incuofvzxXXXXXXXX",
"InstanceId.3": "i-bp1incuofvzxXXXXXXXX",
"InstanceId.4": "i-bp10igfmnyttXXXXXXXX",
"InstanceId.5": "i-bp10igfmnyttXXXXXXXX",
"InstanceId.6": "i-bp10igfmnyttXXXXXXXX",
"InstanceId.7": "i-bp10igfmnyttXXXXXXXX",
"InstanceId.8": "i-bp10igfmnyttXXXXXXXX",
"InstanceId.9": "i-bp10igfmnyttXXXXXXXX"
}
Exemple 2 : {"ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd","RegionId":"cn-shanghai","Tag":[{"tag1":"value1","tag2":"value2"}]} doit être converti en :
{
"ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd",
"RegionId":"cn-shanghai",
"Tag.1.tag1":"value1",
"Tag.1.tag2":"value2"
}
Méthode de signature
Les requêtes sont signées et authentifiées à l'aide d'une paire AccessKey (un AccessKey ID et un AccessKey Secret). Pour chaque requête HTTP ou HTTPS, la passerelle API d'Alibaba Cloud recalcule la signature en fonction des paramètres de la requête et compare la signature calculée avec celle incluse dans la requête. Cette procédure permet de vérifier l'identité du demandeur et de garantir l'intégrité ainsi que la sécurité des données transmises.
Les quatre étapes suivantes permettent de générer la valeur de l'en-tête de requête Authorization.
Étape 1 : Construire une requête canonique
Le pseudocode suivant illustre la construction d'une requête canonique (CanonicalRequest) :
CanonicalRequest =
HTTPRequestMethod + '\n' + // The HTTP request method, in uppercase letters.
CanonicalURI + '\n' + // The canonical URI.
CanonicalQueryString + '\n' + // The canonical query string.
CanonicalHeaders + '\n' + // The canonical headers.
SignedHeaders + '\n' + // The signed headers.
HashedRequestPayload // The hash value of the request body.
HTTPRequestMethod (méthode de requête) : nom de la méthode HTTP en majuscules, par exemple GET ou POST.
CanonicalURI (URI canonique) : chemin d'accès à la ressource encodé de l'URL. Le chemin d'accès correspond à la partie située entre l'hôte et la chaîne de requête dans l'URL. Il inclut la barre oblique (/) qui suit l'hôte, mais exclut le point d'interrogation (?) précédant la chaîne de requête. Utilisez l'URI canonique lors de l'envoi d'une requête. Encodez chaque partie de l'URI (chaque chaîne séparée par une barre oblique (/)) dans le jeu de caractères UTF-8, conformément aux règles définies dans RFC3986 :
Les caractères A à Z, a à z et 0 à 9, ainsi que les caractères
-,_,.et~ne sont pas encodés. Les autres caractères sont encodés sous la forme d'un signe pourcentage (%) suivi du code ASCII hexadécimal du caractère. Par exemple, un guillemet double (") est encodé en%22. Les séquences suivantes nécessitent un traitement particulier :
| Séquence | Résultat requis |
| Un espace ( ) | %20 |
Un astérisque (*) |
%2A |
%7E dans la sortie encodée |
Un tilde (~) |
Si vous utilisez java.net.URLEncoder de la bibliothèque standard Java, commencez par encoder la chaîne en appelant encode depuis cette bibliothèque. Ensuite, dans la chaîne encodée, remplacez les signes plus (+) par %20, les astérisques (*) par %2A et %7E par des tildes (~). Le résultat obtenu est une chaîne encodée conforme aux règles précédentes.
Pour une API de style RPC, utilisez une barre oblique (/) comme CanonicalURI.
Pour une API de style ROA, ce paramètre correspond à la valeur de path dans les métadonnées OpenAPI, par exemple /api/v1/clusters.
CanonicalQueryString (chaîne de requête canonique) : dans les métadonnées OpenAPI, si les paramètres de requête d'une API contiennent "in":"query", concaténez ces paramètres de requête selon les règles suivantes :
Triez les paramètres de requête par ordre croissant en fonction du nom du paramètre.
Encodez séparément le nom et la valeur de chaque paramètre dans le jeu de caractères UTF-8, conformément aux règles définies dans RFC3986. Ces règles sont identiques à celles d'encodage de CanonicalURI décrites dans la section précédente.
Utilisez un signe égal (
=) pour relier le nom du paramètre encodé à sa valeur encodée. Si un paramètre n'a aucune valeur, utilisez une chaîne vide comme valeur.Utilisez des esperluettes (
&) pour relier plusieurs paramètres de requête.Si un paramètre de requête est une chaîne JSON, l'ordre des paramètres dans la chaîne JSON n'affecte pas le calcul de la signature.
S'il n'existe aucune chaîne de requête, utilisez une chaîne vide comme chaîne de requête canonique. Exemple :
ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai
HashedRequestPayload : hachez le corps de la requête, puis encodez la valeur de hachage en Base16 pour obtenir HashedRequestPayload. Définissez l'en-tête de requête x-acs-content-sha256 sur la valeur de HashedRequestPayload. Pour la liste des en-têtes de requête courants, consultez la section . Le pseudocode est le suivant :
HashedRequestPayload = HexEncode(Hash(RequestBody))
-
Dans les métadonnées OpenAPI, si les paramètres de requête d'une API contiennent
"in": "body"ou"in": "formData", transmettez les paramètres dans le corps de la requête :Si aucun paramètre de requête n'est transmis dans le corps de la requête, définissez le corps de la requête sur une chaîne vide.
Si les paramètres de requête contiennent
"in": "formData", concaténez les paramètres dans une chaîne au formatkey1=value1&key2=value2&key3=value3et ajoutezcontent-type=application/x-www-form-urlencodedaux en-têtes de requête. Notez que si un paramètre de requête est de type tableau ou objet, vous devez convertir la valeur du paramètre en paires clé-valeur indexées.-
Si les paramètres de requête contiennent
"in": "body", ajoutez content-type aux en-têtes de requête. La valeur de content-type dépend du type de contenu de la requête. Exemples :Si le contenu de la requête est au format JSON, définissez content-type sur
application/json.Si le contenu de la requête est au format binaire, tel qu'un flux de fichier binaire, définissez content-type sur
application/octet-stream.
Hash désigne la fonction de condensé de message. Seul l'algorithme SHA256 est pris en charge.
HexEncode désigne la fonction d'encodage qui renvoie le condensé en notation hexadécimale minuscule, c'est-à-dire l'encodage Base16. Valeur d'exemple de HashedRequestPayload lorsque le corps de la requête est vide :
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
CanonicalHeaders (en-têtes canoniques) : concaténez les en-têtes de requête courants selon les règles suivantes. Pour la liste des en-têtes de requête courants, consultez la section .
Sélectionnez les en-têtes de requête préfixés par
x-acs-, ainsi que les en-têteshostetcontent-type.Convertissez les noms d'en-tête en minuscules et triez les en-têtes par ordre croissant.
Supprimez les espaces de début et de fin des valeurs d'en-tête.
Reliez chaque nom d'en-tête à sa valeur à l'aide d'un deux-points (
:) et ajoutez un saut de ligne (\n) pour former une entrée d'en-tête canonique (CanonicalHeaderEntry).Concaténez plusieurs entrées d'en-tête canonique (CanonicalHeaderEntry) en une seule chaîne. Tous les en-têtes de requête, à l'exception de Authorization, doivent être utilisés pour le calcul de la signature s'ils satisfont aux exigences précédentes.
Le pseudocode est le suivant :
CanonicalHeaderEntry = Lowercase(HeaderName) + ':' + Trim(HeaderValue) + '\n'
CanonicalHeaders =
CanonicalHeaderEntry0 + CanonicalHeaderEntry1 + ... + CanonicalHeaderEntryN
Exemple :
host:ecs.cn-shanghai.aliyuncs.com
x-acs-action:RunInstances
x-acs-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-acs-date:2023-10-26T10:22:32Z
x-acs-signature-nonce:3156853299f313e23d1673dc12e1703d
x-acs-version:2014-05-26
SignedHeaders (liste des en-têtes signés) : spécifie les en-têtes de requête courants utilisés pour le calcul de la signature dans la requête actuelle. Les noms correspondent un à un aux noms de paramètres dans CanonicalHeaders. Construisez la liste selon les règles suivantes :
Convertissez les noms des en-têtes de requête dans CanonicalHeaders en minuscules.
Triez les noms par ordre alphabétique croissant et séparez-les par des points-virgules (
;). Le pseudocode est le suivant :
SignedHeaders = Lowercase(HeaderName0) + ';' + Lowercase(HeaderName1) + ... + Lowercase(HeaderNameN)
Exemple :
host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version
Étape 2 : Construire une chaîne à signer
Construisez une chaîne à signer (stringToSign) selon le pseudocode suivant :
StringToSign =
SignatureAlgorithm + '\n' +
HashedCanonicalRequest
SignatureAlgorithm : seul l'algorithme ACS3-HMAC-SHA256 est pris en charge pour le protocole de signature.
HashedCanonicalRequest : chaîne de condensé de la requête canonique. Le pseudocode suivant indique comment calculer la chaîne de condensé :
HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))
Hash désigne la fonction de condensé de message. Seul l'algorithme SHA256 est pris en charge.
HexEncode désigne la fonction d'encodage qui renvoie le condensé en notation hexadécimale minuscule, c'est-à-dire l'encodage Base16. Exemple :
ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259
Étape 3 : Calculer la chaîne de signature
Calculez la signature (Signature) selon le pseudocode suivant.
Signature = HexEncode(SignatureMethod(Secret, StringToSign))
StringToSign : la chaîne à signer construite à l'étape 2, encodée en UTF-8.
SignatureMethod : HMAC-SHA256 est utilisé comme algorithme de signature.
Secret : l'AccessKey Secret.
HexEncode : la fonction d'encodage qui renvoie le condensé en notation hexadécimale minuscule, c'est-à-dire l'encodage Base16. Exemple :
06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
Étape 4 : Ajouter la chaîne de signature à la requête
Une fois la signature calculée, construisez l'en-tête de requête Authorization au format suivant : Authorization: <SignatureAlgorithm> Credential=<AccessKeyId>,SignedHeaders=<SignedHeaders>,Signature=<Signature>.
Exemple :
ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
Vérifier votre implémentation de signature
Exécutez les quatre étapes précédentes avec les valeurs de paramètres supposées suivantes, puis comparez votre sortie avec la sortie attendue de chaque étape. Identifiez toute divergence avant d'envoyer une requête réelle : une incompatibilité à n'importe quelle étape produit une signature rejetée par la passerelle.
| Paramètre requis | Valeur supposée |
| AccessKeyID | YourAccessKeyId |
| AccessKeySecret | YourAccessKeySecret |
| x-acs-signature-nonce | 3156853299f313e23d1673dc12e1703d |
| x-acs-date | 2023-10-26T10:22:32Z |
| x-acs-action | RunInstances |
| x-acs-version | 2014-05-26 |
| host | ecs.cn-shanghai.aliyuncs.com |
Les paramètres de requête API suivants sont utilisés :
| Paramètre | Valeur |
| ImageId | win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd |
| RegionId | cn-shanghai |
Étape 1 : Construire une requête canonique
POST
/
ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai
host:ecs.cn-shanghai.aliyuncs.com
x-acs-action:RunInstances
x-acs-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-acs-date:2023-10-26T10:22:32Z
x-acs-signature-nonce:3156853299f313e23d1673dc12e1703d
x-acs-version:2014-05-26
host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Étape 2 : Construire une chaîne à signer
ACS3-HMAC-SHA256
7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259
Étape 3 : Calculer la chaîne de signature
06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
Étape 4 : Ajouter la chaîne de signature à la requête
POST /?ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai HTTP/1.1
Authorization: ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
x-acs-action: RunInstances
host: ecs.cn-shanghai.aliyuncs.com
x-acs-date: 2023-10-26T10:22:32Z
x-acs-version: 2014-05-26
x-acs-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-acs-signature-nonce: 3156853299f313e23d1673dc12e1703d
user-agent: AlibabaCloud (Mac OS X; x86_64) Java/1.8.0_352-b08 tea-util/0.2.6 TeaDSL/1
accept: application/json
Les en-têtes user-agent et accept de la requête précédente ne figurent pas dans SignedHeaders. Ils ne participent pas au calcul de la signature.
Si votre sortie diffère de la sortie attendue d'une étape, corrigez cette étape avant de continuer. Si une requête échoue toujours avec une erreur de signature, utilisez les causes et solutions de la section FAQ de cette rubrique pour identifier la divergence.
Exemples de signature
Les exemples suivants présentent des implémentations complètes de la méthode de signature décrite précédemment dans les principaux langages de programmation. Le code fourni vise uniquement à vous aider à comprendre le mécanisme de signature et n'est pas universellement applicable. Chaque onglet indique la version d'exécution avec laquelle l'exemple a été validé, ainsi que les dépendances à installer.
Alibaba Cloud propose également des SDK pour plusieurs langages de programmation et frameworks de développement. Ces SDK gèrent le processus de signature à votre place et vous aident à construire rapidement des applications sur Alibaba Cloud. (Recommandé) Privilégiez l'utilisation d'un SDK plutôt que la signature manuelle des requêtes.
Java
L'exemple de code s'exécute avec JDK 1.8. Vous devrez peut-être adapter le code en fonction de votre environnement réel.
Pour exécuter l'exemple Java, ajoutez les dépendances Maven suivantes au fichier pom.xml.
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
public class SignatureDemo {
public static class SignatureRequest {
// HTTP Method
private final String httpMethod;
// The request path.
private final String canonicalUri;
// endpoint
private final String host;
// API name
private final String xAcsAction;
// API version
private final String xAcsVersion;
// headers
private final Map<String, String> headers = new TreeMap<>();
// The body parameters.
private byte[] body;
// The query parameters.
private final Map<String, Object> queryParam = new TreeMap<>();
public SignatureRequest(String httpMethod, String canonicalUri, String host,
String xAcsAction, String xAcsVersion) {
this.httpMethod = httpMethod;
this.canonicalUri = canonicalUri;
this.host = host;
this.xAcsAction = xAcsAction;
this.xAcsVersion = xAcsVersion;
initHeader();
}
private void initHeader() {
headers.put("host", host);
headers.put("x-acs-action", xAcsAction);
headers.put("x-acs-version", xAcsVersion);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
headers.put("x-acs-date", sdf.format(new Date()));
headers.put("x-acs-signature-nonce", UUID.randomUUID().toString());
}
public String getHttpMethod() {
return httpMethod;
}
public String getCanonicalUri() {
return canonicalUri;
}
public String getHost() {
return host;
}
public Map<String, String> getHeaders() {
return headers;
}
public byte[] getBody() {
return body;
}
public Map<String, Object> getQueryParam() {
return queryParam;
}
public void setBody(byte[] body) {
this.body = body;
}
public void setQueryParam(String key, Object value) {
this.queryParam.put(key, value);
}
public void setHeaders(String key, String value) {
this.headers.put(key, value);
}
}
public static class SignatureService {
private static final String ALGORITHM = "ACS3-HMAC-SHA256";
/**
* Calculate the signature.
*/
public static void getAuthorization(SignatureRequest signatureRequest,
String accessKeyId, String accessKeySecret, String securityToken) {
try {
// Process complex query parameters.
Map<String, Object> processedQueryParams = new TreeMap<>();
processObject(processedQueryParams, "", signatureRequest.getQueryParam());
signatureRequest.getQueryParam().clear();
signatureRequest.getQueryParam().putAll(processedQueryParams);
// Step 1: Construct the canonical request string.
String canonicalQueryString = buildCanonicalQueryString(signatureRequest.getQueryParam());
// Calculate the hash value of the request body.
String hashedRequestPayload = calculatePayloadHash(signatureRequest.getBody());
signatureRequest.setHeaders("x-acs-content-sha256", hashedRequestPayload);
// Add the security token if it exists.
if (securityToken != null && !securityToken.isEmpty()) {
signatureRequest.setHeaders("x-acs-security-token", securityToken);
}
// Construct the canonical headers and the signed headers.
CanonicalHeadersResult canonicalHeadersResult = buildCanonicalHeaders(signatureRequest.getHeaders());
// Construct the canonical request.
String canonicalRequest = String.join("\n",
signatureRequest.getHttpMethod(),
signatureRequest.getCanonicalUri(),
canonicalQueryString,
canonicalHeadersResult.canonicalHeaders,
canonicalHeadersResult.signedHeaders,
hashedRequestPayload);
System.out.println("canonicalRequest=========>\n" + canonicalRequest);
// Step 2: Construct the string-to-sign.
String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8));
String stringToSign = ALGORITHM + "\n" + hashedCanonicalRequest;
System.out.println("stringToSign=========>\n" + stringToSign);
// Step 3: Calculate the signature.
String signature = DatatypeConverter.printHexBinary(
hmac256(accessKeySecret.getBytes(StandardCharsets.UTF_8), stringToSign))
.toLowerCase();
System.out.println("signature=========>" + signature);
// Step 4: Construct the Authorization header.
String authorization = String.format("%s Credential=%s,SignedHeaders=%s,Signature=%s",
ALGORITHM, accessKeyId, canonicalHeadersResult.signedHeaders, signature);
System.out.println("authorization=========>" + authorization);
signatureRequest.getHeaders().put("Authorization", authorization);
} catch (Exception e) {
throw new RuntimeException("Failed to generate authorization", e);
}
}
/**
* Process request parameters of the formData type.
*/
private static String formDataToString(Map<String, Object> formData) {
Map<String, Object> tileMap = new HashMap<>();
processObject(tileMap, "", formData);
StringBuilder result = new StringBuilder();
boolean first = true;
String symbol = "&";
for (Map.Entry<String, Object> entry : tileMap.entrySet()) {
String value = String.valueOf(entry.getValue());
if (value != null && !value.isEmpty()) {
if (first) {
first = false;
} else {
result.append(symbol);
}
result.append(percentCode(entry.getKey()));
result.append("=");
result.append(percentCode(value));
}
}
return result.toString();
}
/**
* Construct the canonical query string.
*/
private static String buildCanonicalQueryString(Map<String, Object> queryParams) {
return queryParams.entrySet().stream()
.map(entry -> percentCode(entry.getKey()) + "=" +
percentCode(String.valueOf(entry.getValue())))
.collect(Collectors.joining("&"));
}
/**
* Calculate the hash value of the request body.
*/
private static String calculatePayloadHash(byte[] body) throws Exception {
if (body != null) {
return sha256Hex(body);
} else {
return sha256Hex("".getBytes(StandardCharsets.UTF_8));
}
}
/**
* Construct the canonical headers.
*/
private static CanonicalHeadersResult buildCanonicalHeaders(Map<String, String> headers) {
List<Map.Entry<String, String>> signedHeaders = headers.entrySet().stream()
.filter(entry -> {
String key = entry.getKey().toLowerCase();
return key.startsWith("x-acs-") || "host".equals(key) || "content-type".equals(key);
})
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toList());
StringBuilder canonicalHeaders = new StringBuilder();
StringBuilder signedHeadersString = new StringBuilder();
for (Map.Entry<String, String> entry : signedHeaders) {
String lowerKey = entry.getKey().toLowerCase();
String value = entry.getValue().trim();
canonicalHeaders.append(lowerKey).append(":").append(value).append("\n");
signedHeadersString.append(lowerKey).append(";");
}
if (signedHeadersString.length() > 0) {
signedHeadersString.setLength(signedHeadersString.length() - 1); // Remove the trailing semicolon.
}
return new CanonicalHeadersResult(canonicalHeaders.toString(), signedHeadersString.toString());
}
private static class CanonicalHeadersResult {
final String canonicalHeaders;
final String signedHeaders;
CanonicalHeadersResult(String canonicalHeaders, String signedHeaders) {
this.canonicalHeaders = canonicalHeaders;
this.signedHeaders = signedHeaders;
}
}
/**
* Process complex object parameters.
*/
private static void processObject(Map<String, Object> map, String key, Object value) {
if (value == null) {
return;
}
if (key == null) {
key = "";
}
if (value instanceof List<?>) {
List<?> list = (List<?>) value;
for (int i = 0; i < list.size(); ++i) {
processObject(map, key + "." + (i + 1), list.get(i));
}
} else if (value instanceof Map<?, ?>) {
Map<?, ?> subMap = (Map<?, ?>) value;
for (Map.Entry<?, ?> entry : subMap.entrySet()) {
processObject(map, key + "." + entry.getKey().toString(), entry.getValue());
}
} else {
if (key.startsWith(".")) {
key = key.substring(1);
}
if (value instanceof byte[]) {
map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
} else {
map.put(key, String.valueOf(value));
}
}
}
/**
* Perform the HMAC-SHA256 calculation.
*/
private static byte[] hmac256(byte[] secretKey, String str) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, mac.getAlgorithm());
mac.init(secretKeySpec);
return mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
}
/**
* Perform the SHA-256 hash calculation.
*/
private static String sha256Hex(byte[] input) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(input);
return DatatypeConverter.printHexBinary(digest).toLowerCase();
}
/**
* Perform URL encoding.
*/
public static String percentCode(String str) {
if (str == null) {
return "";
}
try {
return URLEncoder.encode(str, "UTF-8")
.replace("+", "%20")
.replace("*", "%2A")
.replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported", e);
}
}
}
/**
* This is a signature example. Replace the sample parameters in the main method with your actual values.
* ROA-style and RPC-style APIs differ only in how the canonicalUri value is obtained. The rest of the process is the same.
* <p>
* Obtain the request method (methods), request parameter name (name), request parameter type (type), and request parameter position (in) from the API metadata, and then encapsulate the parameters into SignatureRequest.
* 1. If a request parameter is marked "in":"query" in the metadata, pass it in queryParam. Note: For an RPC-style API, this type of parameter can also be passed in the body, with content-type set to application/x-www-form-urlencoded. See example 3.
* 2. If a request parameter is marked "in": "body" in the metadata, pass it in the body, with the MIME type set to application/octet-stream or application/json. Note: For an RPC-style API, application/json is not recommended. Use example 3 instead.
* 3. If a request parameter is marked "in": "formData" in the metadata, pass it in the body, with the MIME type set to application/x-www-form-urlencoded.
*/
public static void main(String[] args) throws IOException {
// Obtain the AccessKey pair from environment variables.
String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
String securityToken = System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN");
if (accessKeyId == null || accessKeySecret == null) {
System.err.println("Set the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables.");
return;
}
// RPC-style API example 1: the request parameter is "in":"query". This example uses the DescribeInstanceStatus operation of ECS.
SignatureRequest signatureRequest = new SignatureRequest(
"POST",
"/",
"ecs.cn-hangzhou.aliyuncs.com",
"DescribeInstanceStatus",
"2014-05-26"
);
signatureRequest.setQueryParam("RegionId", "cn-hangzhou");
signatureRequest.setQueryParam("InstanceId", Arrays.asList("i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"));
/*// RPC-style API example 2: the request parameter is "in":"body" (file upload scenario). This example uses the RecognizeGeneral operation of OCR.
SignatureRequest signatureRequest = new SignatureRequest(
"POST",
"/",
"ocr-api.cn-hangzhou.aliyuncs.com",
"RecognizeGeneral",
"2021-07-07");
signatureRequest.setBody(Files.readAllBytes(Paths.get("D:\\test.jpeg")));
signatureRequest.setHeaders("content-type", "application/octet-stream");*/
/*// RPC-style API example 3: the request parameter is "in": "formData" or "in":"body" (non-file upload scenario). This example uses the TranslateGeneral operation of Machine Translation.
String httpMethod = "POST";
String canonicalUri = "/";
String host = "mt.aliyuncs.com";
String xAcsAction = "TranslateGeneral";
String xAcsVersion = "2018-10-12";
SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
Map<String, Object> body = new HashMap<>();
body.put("FormatType", "text");
body.put("SourceLanguage", "zh");
body.put("TargetLanguage", "en");
body.put("SourceText", "Hello");
body.put("Scene", "general");
String formDataToString = SignatureService.formDataToString(body);
signatureRequest.setBody(formDataToString.getBytes(StandardCharsets.UTF_8));
signatureRequest.setHeaders("content-type", "application/x-www-form-urlencoded");*/
/*// ROA-style API POST request example. This example creates a cluster in Container Service for Kubernetes (ACK).
SignatureRequest signatureRequest = new SignatureRequest(
"POST",
"/clusters",
"cs.cn-chengdu.aliyuncs.com",
"CreateCluster",
"2015-12-15");
TreeMap<String, Object> body = new TreeMap<>();
body.put("name", "Test");
body.put("cluster_type", "ManagedKubernetes");
body.put("kubernetes_version", "1.34.1-aliyun.1");
body.put("region_id", "cn-chengdu");
body.put("snat_entry", true);
body.put("deletion_protection", true);
body.put("proxy_mode", "ipvs");
body.put("profile", "Default");
body.put("timezone", "Asia/Shanghai");
body.put("cluster_spec", "ack.pro.small");
body.put("enable_rrsa", false);
body.put("service_cidr", "192.168.0.0/16");
body.put("zone_ids", Arrays.asList("cn-chengdu-b","cn-chengdu-b"));
Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
signatureRequest.setBody(gson.toJson(body).getBytes(StandardCharsets.UTF_8));
signatureRequest.setHeaders("content-type", "application/json");*/
/*// ROA-style API GET request. This example queries cluster information in Container Service for Kubernetes.
SignatureRequest signatureRequest = new SignatureRequest(
"GET",
"/clusters/" + SignatureService.percentCode("c299f90b63b************") + "/resources",
"cs.cn-chengdu.aliyuncs.com",
"DescribeClusterResources",
"2015-12-15");
signatureRequest.setQueryParam("with_addon_resources", true);*/
/*// ROA-style API DELETE request. This example deletes a cluster.
SignatureRequest signatureRequest = new SignatureRequest(
"DELETE",
"/clusters/" + SignatureService.percentCode("c299f90b63b************"),
"cs.cn-chengdu.aliyuncs.com",
"DeleteCluster",
"2015-12-15");*/
// Generate the signature.
SignatureService.getAuthorization(signatureRequest, accessKeyId, accessKeySecret, securityToken);
// Test whether the API operation can be called successfully.
callApi(signatureRequest);
}
/**
* For testing only.
*/
private static void callApi(SignatureRequest signatureRequest) {
try {
String url = "https://" + signatureRequest.getHost() + signatureRequest.getCanonicalUri();
URIBuilder uriBuilder = new URIBuilder(url);
// Add the query parameters.
for (Map.Entry<String, Object> entry : signatureRequest.getQueryParam().entrySet()) {
uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
HttpUriRequest httpRequest;
switch (signatureRequest.getHttpMethod()) {
case "GET":
httpRequest = new HttpGet(uriBuilder.build());
break;
case "POST":
HttpPost httpPost = new HttpPost(uriBuilder.build());
if (signatureRequest.getBody() != null) {
httpPost.setEntity(new ByteArrayEntity(signatureRequest.getBody(), ContentType.create(signatureRequest.getHeaders().get("content-type"))));
}
httpRequest = httpPost;
break;
case "DELETE":
httpRequest = new HttpDelete(uriBuilder.build());
break;
default:
System.out.println("Unsupported HTTP method: " + signatureRequest.getHttpMethod());
throw new IllegalArgumentException("Unsupported HTTP method");
}
// Add the request headers.
for (Map.Entry<String, String> entry : signatureRequest.getHeaders().entrySet()) {
httpRequest.addHeader(entry.getKey(), entry.getValue());
}
// Send the request.
try (CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(httpRequest)) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println("API Response: " + result);
}
} catch (IOException | URISyntaxException e) {
throw new RuntimeException("Failed to call API", e);
}
}
}Python
L'exemple de code s'exécute avec Python 3.12.3. Vous devrez peut-être adapter le code en fonction de votre environnement réel.
Vous devez installer manuellement pytz et requests. Exécutez les commandes suivantes dans le terminal en fonction de la version de Python que vous utilisez.
Python3
pip3 install pytz
pip3 install requests
import hashlib
import hmac
import json
import os
import uuid
from collections import OrderedDict
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from urllib.parse import quote_plus, urlencode
import pytz
import requests
class SignatureRequest:
def __init__(
self,
http_method: str,
canonical_uri: str,
host: str,
x_acs_action: str,
x_acs_version: str
):
self.http_method = http_method
self.canonical_uri = canonical_uri
self.host = host
self.x_acs_action = x_acs_action
self.x_acs_version = x_acs_version
self.headers = self._init_headers()
self.query_param = OrderedDict() # type: Dict[str, Any]
self.body = None # type: Optional[bytes]
def _init_headers(self) -> Dict[str, str]:
current_time = datetime.now(pytz.timezone('Etc/GMT'))
headers = OrderedDict([
('host', self.host),
('x-acs-action', self.x_acs_action),
('x-acs-version', self.x_acs_version),
('x-acs-date', current_time.strftime('%Y-%m-%dT%H:%M:%SZ')),
('x-acs-signature-nonce', str(uuid.uuid4())),
])
return headers
def sorted_query_params(self) -> None:
"""Sorts query parameters by name and returns the encoded string."""
self.query_param = dict(sorted(self.query_param.items()))
def sorted_headers(self) -> None:
"""Sorts request headers by name and returns the encoded string."""
self.headers = dict(sorted(self.headers.items()))
def get_authorization(request: SignatureRequest) -> None:
try:
new_query_param = OrderedDict()
process_object(new_query_param, '', request.query_param)
request.query_param.clear()
request.query_param.update(new_query_param)
request.sorted_query_params()
# Step 1: Construct the canonical request.
canonical_query_string = "&".join(
f"{percent_code(quote_plus(k))}={percent_code(quote_plus(str(v)))}"
for k, v in request.query_param.items()
)
hashed_request_payload = sha256_hex(request.body or b'')
request.headers['x-acs-content-sha256'] = hashed_request_payload
if SECURITY_TOKEN:
signature_request.headers["x-acs-security-token"] = SECURITY_TOKEN
request.sorted_headers()
filtered_headers = OrderedDict()
for k, v in request.headers.items():
if k.lower().startswith("x-acs-") or k.lower() in ["host", "content-type"]:
filtered_headers[k.lower()] = v
canonical_headers = "\n".join(f"{k}:{v}" for k, v in filtered_headers.items()) + "\n"
signed_headers = ";".join(filtered_headers.keys())
canonical_request = (
f"{request.http_method}\n{request.canonical_uri}\n{canonical_query_string}\n"
f"{canonical_headers}\n{signed_headers}\n{hashed_request_payload}"
)
print(canonical_request)
# Step 2: Construct the string-to-sign.
hashed_canonical_request = sha256_hex(canonical_request.encode("utf-8"))
string_to_sign = f"{ALGORITHM}\n{hashed_canonical_request}"
print(string_to_sign)
# Step 3: Calculate the signature.
signature = hmac256(ACCESS_KEY_SECRET.encode("utf-8"), string_to_sign).hex().lower()
# Step 4: Construct the Authorization header.
authorization = f'{ALGORITHM} Credential={ACCESS_KEY_ID},SignedHeaders={signed_headers},Signature={signature}'
request.headers["Authorization"] = authorization
except Exception as e:
print("Failed to get authorization")
print(e)
def form_data_to_string(form_data: Dict[str, Any]) -> str:
tile_map = OrderedDict()
process_object(tile_map, "", form_data)
return urlencode(tile_map)
def process_object(result_map: Dict[str, str], key: str, value: Any) -> None:
if value is None:
return
if isinstance(value, (list, tuple)):
for i, item in enumerate(value):
process_object(result_map, f"{key}.{i + 1}", item)
elif isinstance(value, dict):
for sub_key, sub_value in value.items():
process_object(result_map, f"{key}.{sub_key}", sub_value)
else:
key = key.lstrip(".")
result_map[key] = value.decode("utf-8") if isinstance(value, bytes) else str(value)
def hmac256(key: bytes, msg: str) -> bytes:
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
def sha256_hex(s: bytes) -> str:
return hashlib.sha256(s).hexdigest()
def call_api(request: SignatureRequest) -> None:
url = f"https://{request.host}{request.canonical_uri}"
if request.query_param:
url += "?" + urlencode(request.query_param, doseq=True, safe="*")
headers = dict(request.headers)
data = request.body
try:
response = requests.request(
method=request.http_method, url=url, headers=headers, data=data
)
response.raise_for_status()
print(response.text)
except requests.RequestException as e:
print("Failed to send request")
print(e)
def percent_code(encoded_str: str) -> str:
return encoded_str.replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
# Obtain the AccessKey ID and AccessKey secret from environment variables.
ACCESS_KEY_ID = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID")
ACCESS_KEY_SECRET = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
SECURITY_TOKEN = os.environ.get("ALIBABA_CLOUD_SECURITY_TOKEN")
ALGORITHM = "ACS3-HMAC-SHA256"
"""
This is a signature example. When you test the example, select a scenario in the main function and modify the sample values. For example, to call SendSms, select example 1 and then modify http_method, host, x_acs_action, x_acs_version, and query_param.
ROA-style and RPC-style APIs differ only in how the canonicalUri value is obtained.
Obtain the request method (methods), request parameter name (name), request parameter type (type), and request parameter position (in) from the OpenAPI metadata, and then encapsulate the parameters into SignatureRequest.
1. If a request parameter is marked "in":"query" in the metadata, pass it in queryParam. You do not need to set content-type. Note: For an RPC-style API, this type of parameter can also be passed in the body, with content-type set to application/x-www-form-urlencoded. See example 3.
2. If a request parameter is marked "in": "body" in the metadata, pass it in the body and set content-type based on your scenario. Note: For an RPC-style API, application/json is not recommended. Use example 3 instead.
3. If a request parameter is marked "in": "formData" in the metadata, pass it in the body, with content-type set to application/x-www-form-urlencoded.
"""
if __name__ == "__main__":
# RPC-style API request example 1: the request parameter is "in":"query".
http_method = "POST" # The request method. You can obtain it from the metadata. POST is recommended.
canonical_uri = "/" # An RPC-style API has no resource path. Therefore, a forward slash (/) is used as the CanonicalURI.
host = "ecs.cn-hangzhou.aliyuncs.com" # The service endpoint of the Alibaba Cloud service.
x_acs_action = "DescribeInstanceStatus" # The API name.
x_acs_version = "2014-05-26" # The API version.
signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# The request parameters of DescribeInstanceStatus are as follows:
# RegionId is displayed as a String in the metadata, "in":"query", and is required.
signature_request.query_param['RegionId'] = 'cn-hangzhou'
# InstanceId is displayed as an array in the metadata, "in":"query", and is optional.
signature_request.query_param['InstanceId'] = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX",
"i-bp1incuofvzxXXXXXXXX"]
# # RPC-style API request example 2: the request parameter is "in":"body" (file upload scenario).
# http_method = "POST"
# canonical_uri = "/"
# host = "ocr-api.cn-hangzhou.aliyuncs.com"
# x_acs_action = "RecognizeGeneral"
# x_acs_version = "2021-07-07"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# # The request parameter is marked "in": "body" in the metadata. Pass it in the body.
# file_path = "D:\\test.png"
# with open(file_path, 'rb') as file:
# # Read the image content into a byte array.
# signature_request.body = file.read()
# signature_request.headers["content-type"] = "application/octet-stream"
# # RPC-style API request example 3: the request parameter is "in": "formData" or "in":"body" (non-file upload scenario).
# http_method = "POST"
# canonical_uri = "/"
# host = "mt.aliyuncs.com"
# x_acs_action = "TranslateGeneral"
# x_acs_version = "2018-10-12"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# # The request parameters of TranslateGeneral are as follows:
# # Context is displayed as a String in the metadata, "in":"query", and is optional.
# signature_request.query_param['Context'] = 'Morning'
# # Parameters such as FormatType, SourceLanguage, and TargetLanguage are marked "in":"formData" in the metadata.
# form_data = OrderedDict()
# form_data["FormatType"] = "text"
# form_data["SourceLanguage"] = "zh"
# form_data["TargetLanguage"] = "en"
# form_data["SourceText"] = "Hello"
# form_data["Scene"] = "general"
# signature_request.body = bytes(form_data_to_string(form_data), 'utf-8')
# signature_request.headers["content-type"] = "application/x-www-form-urlencoded"
# # Example 4: an ROA-style API POST request.
# http_method = "POST"
# canonical_uri = "/clusters"
# host = "cs.cn-beijing.aliyuncs.com"
# x_acs_action = "CreateCluster"
# x_acs_version = "2015-12-15"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# The request parameter is marked "in":"body" in the metadata. Pass it in the body.
# body = OrderedDict()
# body["name"] = "testDemo"
# body["region_id"] = "cn-beijing"
# body["cluster_type"] = "ExternalKubernetes"
# body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX"
# body["container_cidr"] = "172.16.1.0/20"
# body["service_cidr"] = "10.2.0.0/24"
# body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX"
# body["vswitch_ids"] = ["vsw-2zei30dhfldu8XXXXXXXX"]
# signature_request.body = bytes(json.dumps(body, separators=(',', ':')), 'utf-8')
# signature_request.headers["content-type"] = "application/json; charset=utf-8"
# # Example 5: an ROA-style API GET request.
# http_method = "GET"
# # If canonicalUri contains a path parameter, encode the path parameter by using percent_code({path parameter}).
# cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX")
# canonical_uri = f"/clusters/{cluster_id_encode}/resources"
# host = "cs.cn-beijing.aliyuncs.com"
# x_acs_action = "DescribeClusterResources"
# x_acs_version = "2015-12-15"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
# signature_request.query_param['with_addon_resources'] = True
# # Example 6: an ROA-style API DELETE request.
# http_method = "DELETE"
# # If canonicalUri contains a path parameter, encode the path parameter by using percent_code({path parameter}).
# cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX")
# canonical_uri = f"/clusters/{cluster_id_encode}"
# host = "cs.cn-beijing.aliyuncs.com"
# x_acs_action = "DeleteCluster"
# x_acs_version = "2015-12-15"
# signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version)
get_authorization(signature_request)
call_api(signature_request)
Go
L'exemple de code s'exécute avec go1.22.2. Vous devrez peut-être adapter le code en fonction de votre environnement réel.
Exécutez la commande suivante dans le terminal :
go get github.com/google/uuid
go get golang.org/x/exp/mapspackage main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"os"
"sort"
"golang.org/x/exp/maps"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/google/uuid"
)
type Request struct {
httpMethod string
canonicalUri string
host string
xAcsAction string
xAcsVersion string
headers map[string]string
body []byte
queryParam map[string]interface{}
}
func NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion string) *Request {
req := &Request{
httpMethod: httpMethod,
canonicalUri: canonicalUri,
host: host,
xAcsAction: xAcsAction,
xAcsVersion: xAcsVersion,
headers: make(map[string]string),
queryParam: make(map[string]interface{}),
}
req.headers["host"] = host
req.headers["x-acs-action"] = xAcsAction
req.headers["x-acs-version"] = xAcsVersion
req.headers["x-acs-date"] = time.Now().UTC().Format(time.RFC3339)
req.headers["x-acs-signature-nonce"] = uuid.New().String()
return req
}
// os.Getenv() obtains the AccessKey ID and AccessKey secret from environment variables.
var (
AccessKeyId = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
AccessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
SecurityToken = os.Getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
ALGORITHM = "ACS3-HMAC-SHA256"
)
// This is a signature example. Replace the sample parameters in the main method with your actual values.
// ROA-style and RPC-style APIs differ only in how the canonicalUri value is obtained. The rest of the process is the same.
// Obtain the request method (methods), request parameter name (name), request parameter type (type), and request parameter position (in) from the API metadata, and then encapsulate the parameters into SignatureRequest.
// 1. If a request parameter is marked "in":"query" in the metadata, pass it in queryParam. Note: For an RPC-style API, this type of parameter can also be passed in the body, with content-type set to application/x-www-form-urlencoded. See example 3.
// 2. If a request parameter is marked "in": "body" in the metadata, pass it in the body, with the MIME type set to application/octet-stream or application/json. For an RPC-style API, application/json is not recommended. Use example 3 instead.
// 3. If a request parameter is marked "in": "formData" in the metadata, pass it in the body, with the MIME type set to application/x-www-form-urlencoded.
func main() {
// RPC-style API request example 1: the request parameter is "in":"query".
httpMethod := "POST" // The request method. Most RPC-style APIs support both POST and GET. POST is used in this example.
canonicalUri := "/" // An RPC-style API has no resource path. Therefore, a forward slash (/) is used as the CanonicalURI.
host := "ecs.cn-hangzhou.aliyuncs.com" // The service endpoint of the Alibaba Cloud service.
xAcsAction := "DescribeInstanceStatus" // The API name.
xAcsVersion := "2014-05-26" // The API version.
req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// The request parameters of DescribeInstanceStatus are as follows:
// RegionId is displayed as a String in the metadata, "in":"query", and is required.
req.queryParam["RegionId"] = "cn-hangzhou"
// InstanceId is displayed as an array in the metadata, "in":"query", and is optional.
instanceIds := []interface{}{"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"}
req.queryParam["InstanceId"] = instanceIds
// // RPC-style API request example 2: the request parameter is "in":"body" (file upload scenario).
// httpMethod := "POST"
// canonicalUri := "/"
// host := "ocr-api.cn-hangzhou.aliyuncs.com"
// xAcsAction := "RecognizeGeneral"
// xAcsVersion := "2021-07-07"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// // Read the file content.
// filePath := "D:\\test.png"
// bytes, err := os.ReadFile(filePath)
// if err != nil {
// fmt.Println("Error reading file:", err)
// return
// }
// req.body = bytes
// req.headers["content-type"] = "application/octet-stream"
// // RPC-style API request example 3: the request parameter is "in": "formData" or "in":"body" (non-file upload scenario).
// httpMethod := "POST"
// canonicalUri := "/"
// host := "mt.aliyuncs.com"
// xAcsAction := "TranslateGeneral"
// xAcsVersion := "2018-10-12"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// // The request parameters of TranslateGeneral are as follows:
// // Context is displayed as a String in the metadata, "in":"query", and is optional.
// req.queryParam["Context"] = "Morning"
// // Parameters such as FormatType, SourceLanguage, and TargetLanguage are marked "in":"formData" in the metadata.
// body := make(map[string]interface{})
// body["FormatType"] = "text"
// body["SourceLanguage"] = "zh"
// body["TargetLanguage"] = "en"
// body["SourceText"] = "Hello"
// body["Scene"] = "general"
// str := formDataToString(body)
// req.body = []byte(*str)
// req.headers["content-type"] = "application/x-www-form-urlencoded"
// // An ROA-style API POST request.
// httpMethod := "POST"
// canonicalUri := "/clusters"
// host := "cs.cn-beijing.aliyuncs.com"
// xAcsAction := "CreateCluster"
// xAcsVersion := "2015-12-15"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// // Encapsulate the request parameters. A request parameter that is marked "in": "body" in the metadata is passed in the body.
// body := make(map[string]interface{})
// body["name"] = "testDemo"
// body["region_id"] = "cn-beijing"
// body["cluster_type"] = "ExternalKubernetes"
// body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX"
// body["container_cidr"] = "10.0.0.0/8"
// body["service_cidr"] = "172.16.1.0/20"
// body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX"
// vswitch_ids := []interface{}{"vsw-2zei30dhfldu8XXXXXXXX"}
// body["vswitch_ids"] = vswitch_ids
// jsonBytes, err := json.Marshal(body)
// if err != nil {
// fmt.Println("Error marshaling to JSON:", err)
// return
// }
// req.body = []byte(jsonBytes)
// req.headers["content-type"] = "application/json; charset=utf-8"
// // An ROA-style API GET request.
// httpMethod := "GET"
// // If canonicalUri contains a path parameter, encode the path parameter by using percentCode({path parameter}).
// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66") + "/resources"
// host := "cs.cn-beijing.aliyuncs.com"
// xAcsAction := "DescribeClusterResources"
// xAcsVersion := "2015-12-15"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// req.queryParam["with_addon_resources"] = "true"
// // An ROA-style API DELETE request.
// httpMethod := "DELETE"
// // If canonicalUri contains a path parameter, encode the path parameter by using percentCode({path parameter}).
// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66")
// host := "cs.cn-beijing.aliyuncs.com"
// xAcsAction := "DeleteCluster"
// xAcsVersion := "2015-12-15"
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
// Sign the request.
getAuthorization(req)
// Call the API operation.
error := callAPI(req)
if error != nil {
println(error.Error())
}
}
func callAPI(req *Request) error {
urlStr := "https://" + req.host + req.canonicalUri
q := url.Values{}
keys := maps.Keys(req.queryParam)
sort.Strings(keys)
for _, k := range keys {
v := req.queryParam[k]
q.Set(k, fmt.Sprintf("%v", v))
}
urlStr += "?" + q.Encode()
fmt.Println(urlStr)
httpReq, err := http.NewRequest(req.httpMethod, urlStr, strings.NewReader(string(req.body)))
if err != nil {
return err
}
for key, value := range req.headers {
httpReq.Header.Set(key, value)
}
client := &http.Client{}
resp, err := client.Do(httpReq)
if err != nil {
return err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
var respBuffer bytes.Buffer
_, err = io.Copy(&respBuffer, resp.Body)
if err != nil {
return err
}
respBytes := respBuffer.Bytes()
fmt.Println(string(respBytes))
return nil
}
func getAuthorization(req *Request) {
// Flatten the query parameters whose values are of the List or Map type in queryParam.
newQueryParams := make(map[string]interface{})
processObject(newQueryParams, "", req.queryParam)
req.queryParam = newQueryParams
// Step 1: Construct the canonical request.
canonicalQueryString := ""
keys := maps.Keys(req.queryParam)
sort.Strings(keys)
for _, k := range keys {
v := req.queryParam[k]
canonicalQueryString += percentCode(url.QueryEscape(k)) + "=" + percentCode(url.QueryEscape(fmt.Sprintf("%v", v))) + "&"
}
canonicalQueryString = strings.TrimSuffix(canonicalQueryString, "&")
fmt.Printf("canonicalQueryString========>%s\n", canonicalQueryString)
var bodyContent []byte
if req.body == nil {
bodyContent = []byte("")
} else {
bodyContent = req.body
}
hashedRequestPayload := sha256Hex(bodyContent)
req.headers["x-acs-content-sha256"] = hashedRequestPayload
if SecurityToken != "" {
req.headers["x-acs-security-token"] = SecurityToken
}
canonicalHeaders := ""
signedHeaders := ""
HeadersKeys := maps.Keys(req.headers)
sort.Strings(HeadersKeys)
for _, k := range HeadersKeys {
lowerKey := strings.ToLower(k)
if lowerKey == "host" || strings.HasPrefix(lowerKey, "x-acs-") || lowerKey == "content-type" {
canonicalHeaders += lowerKey + ":" + req.headers[k] + "\n"
signedHeaders += lowerKey + ";"
}
}
signedHeaders = strings.TrimSuffix(signedHeaders, ";")
canonicalRequest := req.httpMethod + "\n" + req.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload
fmt.Printf("canonicalRequest========>\n%s\n", canonicalRequest)
// Step 2: Construct the string-to-sign.
hashedCanonicalRequest := sha256Hex([]byte(canonicalRequest))
stringToSign := ALGORITHM + "\n" + hashedCanonicalRequest
fmt.Printf("stringToSign========>\n%s\n", stringToSign)
// Step 3: Calculate the signature.
byteData, err := hmac256([]byte(AccessKeySecret), stringToSign)
if err != nil {
fmt.Println(err)
panic(err)
}
signature := strings.ToLower(hex.EncodeToString(byteData))
// Step 4: Construct the Authorization header.
authorization := ALGORITHM + " Credential=" + AccessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature
req.headers["Authorization"] = authorization
}
func hmac256(key []byte, toSignString string) ([]byte, error) {
// Instantiate the HMAC-SHA256 hash.
h := hmac.New(sha256.New, key)
// Write the string-to-sign.
_, err := h.Write([]byte(toSignString))
if err != nil {
return nil, err
}
// Calculate the signature and return it.
return h.Sum(nil), nil
}
func sha256Hex(byteArray []byte) string {
// Instantiate the SHA-256 hash function.
hash := sha256.New()
// Write the string to the hash function.
_, _ = hash.Write(byteArray)
// Calculate the SHA-256 hash value and convert it into a lowercase hexadecimal string.
hexString := hex.EncodeToString(hash.Sum(nil))
return hexString
}
func percentCode(str string) string {
// Replace specific encoded characters.
str = strings.ReplaceAll(str, "+", "%20")
str = strings.ReplaceAll(str, "*", "%2A")
str = strings.ReplaceAll(str, "%7E", "~")
return str
}
func formDataToString(formData map[string]interface{}) *string {
tmp := make(map[string]interface{})
processObject(tmp, "", formData)
res := ""
urlEncoder := url.Values{}
for key, value := range tmp {
v := fmt.Sprintf("%v", value)
urlEncoder.Add(key, v)
}
res = urlEncoder.Encode()
return &res
}
// processObject recursively processes objects and flattens complex objects such as Map and List into key-value pairs.
func processObject(mapResult map[string]interface{}, key string, value interface{}) {
if value == nil {
return
}
switch v := value.(type) {
case []interface{}:
for i, item := range v {
processObject(mapResult, fmt.Sprintf("%s.%d", key, i+1), item)
}
case map[string]interface{}:
for subKey, subValue := range v {
processObject(mapResult, fmt.Sprintf("%s.%s", key, subKey), subValue)
}
default:
if strings.HasPrefix(key, ".") {
key = key[1:]
}
if b, ok := v.([]byte); ok {
mapResult[key] = string(b)
} else {
mapResult[key] = fmt.Sprintf("%v", v)
}
}
}Node.js
L'exemple de code s'exécute avec Node.js v20.13.1. Vous devrez peut-être adapter le code en fonction de votre environnement réel.
Cet exemple utilise Node.js.
const crypto = require('crypto');
const fs = require('fs');
class Request {
constructor(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion) {
this.httpMethod = httpMethod;
this.canonicalUri = canonicalUri || '/';
this.host = host;
this.xAcsAction = xAcsAction;
this.xAcsVersion = xAcsVersion;
this.headers = {};
this.body = null;
this.queryParam = {};
this.initHeader();
}
initHeader() {
const date = new Date();
this.headers = {
'host': this.host,
'x-acs-action': this.xAcsAction,
'x-acs-version': this.xAcsVersion,
'x-acs-date': date.toISOString().replace(/\..+/, 'Z'),
'x-acs-signature-nonce': crypto.randomBytes(16).toString('hex')
}
}
}
const ALGORITHM = 'ACS3-HMAC-SHA256';
const accessKeyId = process.env.ALIBABA_CLOUD_ACCESS_KEY_ID;
const accessKeySecret = process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET;
const securityToken = process.env.ALIBABA_CLOUD_SECURITY_TOKEN;
const encoder = new TextEncoder()
if (!accessKeyId || !accessKeySecret) {
console.error('ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables must be set.');
process.exit(1);
}
function getAuthorization(signRequest) {
try {
newQueryParam = {};
processObject(newQueryParam, "", signRequest.queryParam);
signRequest.queryParam = newQueryParam;
// Step 1: Construct the canonical request.
const canonicalQueryString = Object.entries(signRequest.queryParam)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${percentCode(key)}=${percentCode(value)}`)
.join('&');
// The request body. If the request body is empty, such as in a GET request, RequestPayload is always an empty string.
const requestPayload = signRequest.body || encoder.encode('');
const hashedRequestPayload = sha256Hex(requestPayload);
signRequest.headers['x-acs-content-sha256'] = hashedRequestPayload;
if (securityToken) {
signRequest.headers['x-acs-security-token'] = securityToken;
}
// Convert all keys to lowercase.
signRequest.headers = Object.fromEntries(
Object.entries(signRequest.headers).map(([key, value]) => [key.toLowerCase(), value])
);
const sortedKeys = Object.keys(signRequest.headers)
.filter(key => key.startsWith('x-acs-') || key === 'host' || key === 'content-type')
.sort();
// The signed header list. The lowercase header names are sorted in ascending alphabetical order and separated with semicolons (;).
const signedHeaders = sortedKeys.join(";")
// Construct the request headers. Multiple canonical headers are sorted in ascending order by the character code of the lowercase header name and then concatenated.
const canonicalHeaders = sortedKeys.map(key => `${key}:${signRequest.headers[key]}`).join('\n') + '\n';
const canonicalRequest = [
signRequest.httpMethod,
signRequest.canonicalUri,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
hashedRequestPayload
].join('\n');
console.log('canonicalRequest=========>\n', canonicalRequest);
// Step 2: Construct the string-to-sign.
const hashedCanonicalRequest = sha256Hex(encoder.encode(canonicalRequest));
const stringToSign = `${ALGORITHM}\n${hashedCanonicalRequest}`;
console.log('stringToSign=========>', stringToSign);
// Step 3: Calculate the signature.
const signature = hmac256(accessKeySecret, stringToSign);
console.log('signature=========>', signature);
// Step 4: Construct the Authorization header.
const authorization = `${ALGORITHM} Credential=${accessKeyId},SignedHeaders=${signedHeaders},Signature=${signature}`;
console.log('authorization=========>', authorization);
signRequest.headers['Authorization'] = authorization;
} catch (error) {
console.error('Failed to get authorization');
console.error(error);
}
}
async function callApi(signRequest) {
try {
let url = `https://${signRequest.host}${signRequest.canonicalUri}`;
// Add the request parameters.
if (signRequest.queryParam) {
const query = new URLSearchParams(signRequest.queryParam);
url += '?' + query.toString();
}
console.log('url=========>', url);
// Configure the request options.
let options = {
method: signRequest.httpMethod.toUpperCase(),
headers: signRequest.headers
};
// Process the request body.
if (signRequest.body && ['POST', 'PUT'].includes(signRequest.httpMethod.toUpperCase())) {
options.body = signRequest.body;
}
return (await fetch(url, options)).text();
} catch (error) {
console.error('Failed to send request:', error);
}
}
function percentCode(str) {
return encodeURIComponent(str)
.replace(/\+/g, '%20')
.replace(/\*/g, '%2A')
.replace(/~/g, '%7E');
}
function hmac256(key, data) {
const hmac = crypto.createHmac('sha256', key);
hmac.update(data, 'utf8');
return hmac.digest('hex').toLowerCase();
}
function sha256Hex(bytes) {
const hash = crypto.createHash('sha256');
const digest = hash.update(bytes).digest('hex');
return digest.toLowerCase();
}
function formDataToString(formData) {
const tmp = {};
processObject(tmp, "", formData);
let queryString = '';
for (let [key, value] of Object.entries(tmp)) {
if (queryString !== '') {
queryString += '&';
}
queryString += encodeURIComponent(key) + '=' + encodeURIComponent(value);
}
return queryString;
}
function processObject(map, key, value) {
// If the value is empty, no further processing is required.
if (value === null) {
return;
}
if (key === null) {
key = "";
}
// If the value is of the Array type, iterate over each element in the array and process it recursively.
if (Array.isArray(value)) {
value.forEach((item, index) => {
processObject(map, `${key}.${index + 1}`, item);
});
} else if (typeof value === 'object' && value !== null) {
// If the value is of the Object type, iterate over each key-value pair in the object and process it recursively.
Object.entries(value).forEach(([subKey, subValue]) => {
processObject(map, `${key}.${subKey}`, subValue);
});
} else {
// For a key that starts with a period (.), remove the leading period to keep the keys continuous.
if (key.startsWith('.')) {
key = key.slice(1);
}
map[key] = String(value);
}
}
/**
* This is a signature example. Replace the sample parameters in the main method with your actual values.
* ROA-style and RPC-style APIs differ only in how the canonicalUri value is obtained. The rest of the process is the same.
*
* Obtain the request method (methods), request parameter name (name), request parameter type (type), and request parameter position (in) from the API metadata, and then encapsulate the parameters into SignatureRequest.
* 1. If a request parameter is marked "in":"query" in the metadata, pass it in queryParam. Note: For an RPC-style API, this type of parameter can also be passed in the body, with content-type set to application/x-www-form-urlencoded. See example 3.
* 2. If a request parameter is marked "in": "body" in the metadata, pass it in the body, with the MIME type set to application/octet-stream or application/json. For an RPC-style API, application/json is not recommended. Use example 3 instead.
* 3. If a request parameter is marked "in": "formData" in the metadata, pass it in the body, with the MIME type set to application/x-www-form-urlencoded.
*/
// RPC-style API request example 1: the request parameter is "in":"query".
const httpMethod = 'POST'; // The request method. Most RPC-style APIs support both POST and GET. POST is used in this example.
const canonicalUri = '/'; // An RPC-style API has no resource path. Therefore, a forward slash (/) is used as the CanonicalURI.
const host = 'ecs.cn-hangzhou.aliyuncs.com'; // endpoint
const xAcsAction = 'DescribeInstanceStatus'; // The API name.
const xAcsVersion = '2014-05-26'; // The API version.
const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// The request parameters of DescribeInstanceStatus are as follows:
signRequest.queryParam = {
// RegionId is displayed as a String in the metadata, "in":"query", and is required.
RegionId: 'cn-hangzhou',
// InstanceId is displayed as an array in the metadata, "in":"query", and is optional.
InstanceId: ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"],
}
// // RPC-style API request example 2: the request parameter is "in":"body" (file upload scenario).
// const httpMethod = 'POST';
// const canonicalUri = '/';
// const host = 'ocr-api.cn-hangzhou.aliyuncs.com';
// const xAcsAction = 'RecognizeGeneral';
// const xAcsVersion = '2021-07-07';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// const filePath = 'D:\\test.png';
// const bytes = fs.readFileSync(filePath);
// // A request parameter that is marked "in": "body" in the metadata is passed in the body.
// signRequest.body = bytes;
// signRequest.headers['content-type'] = 'application/octet-stream';
// // RPC-style API request example 3: the request parameter is "in": "formData" or "in":"body" (non-file upload scenario).
// const httpMethod = 'POST'; // The request method. Most RPC-style APIs support both POST and GET. POST is used in this example.
// const canonicalUri = '/'; // An RPC-style API has no resource path. Therefore, a forward slash (/) is used as the CanonicalURI.
// const host = 'mt.aliyuncs.com'; // endpoint
// const xAcsAction = 'TranslateGeneral'; // The API name.
// const xAcsVersion = '2018-10-12'; // The API version.
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// // The request parameters of TranslateGeneral are as follows:
// // Context is displayed as a String in the metadata, "in":"query", and is optional.
// signRequest.queryParam["Context"] = "Morning";
// // Parameters such as FormatType, SourceLanguage, and TargetLanguage are marked "in":"formData" in the metadata.
// const formData = {
// SourceLanguage: "zh",
// TargetLanguage: "en",
// FormatType: "text",
// Scene: "general",
// SourceText: 'Hello'
// }
// const str = formDataToString(formData)
// signRequest.body = encoder.encode(str);
// signRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
// // An ROA-style API POST request.
// const httpMethod = 'POST';
// const canonicalUri = '/clusters';
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'CreateCluster';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// // A request parameter that is marked "in": "body" in the metadata is passed in the body.
// const body = {
// name: 'testDemo',
// region_id: 'cn-beijing',
// cluster_type: 'ExternalKubernetes',
// vpcid: 'vpc-2zeou1uod4ylaf35teei9',
// container_cidr: '10.0.0.0/8',
// service_cidr: '172.16.3.0/20',
// security_group_id: 'sg-2ze1a0rlgeo7dj37dd1q',
// vswitch_ids: [
// 'vsw-2zei30dhfldu8ytmtarro'
// ],
// }
// signRequest.body = encoder.encode(JSON.stringify(body));
// signRequest.headers['content-type'] = 'application/json';
// // An ROA-style API GET request.
// const httpMethod = 'GET';
// // If canonicalUri contains a path parameter, encode the path parameter by using percentCode({path parameter}).
// const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96") + '/resources';
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'DescribeClusterResources';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
// signRequest.queryParam = {
// with_addon_resources: true,
// }
// // An ROA-style API DELETE request.
// const httpMethod = 'DELETE';
// // If canonicalUri contains a path parameter, encode the path parameter by using percentCode({path parameter}).
// const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96");
// const host = 'cs.cn-beijing.aliyuncs.com';
// const xAcsAction = 'DeleteCluster';
// const xAcsVersion = '2015-12-15';
// const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion);
getAuthorization(signRequest);
// Call the API operation.
callApi(signRequest).then(r => {
console.log(r);
}).catch(error => {
console.error(error);
});
PHP
L'exemple de code s'exécute avec PHP 7.4.33. Vous devrez peut-être adapter le code en fonction de votre environnement réel.
<?php
class SignatureDemo
{
// The encryption algorithm.
private $ALGORITHM;
// Access Key ID
private $AccessKeyId;
// Access Key Secret
private $AccessKeySecret;
private $SecurityToken;
public function __construct()
{
date_default_timezone_set('UTC'); // Set the time zone to GMT.
$this->AccessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); // getenv() obtains the AccessKey ID of the RAM user from environment variables.
$this->AccessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); // getenv() obtains the AccessKey secret of the RAM user from environment variables.
$this->SecurityToken = getenv('ALIBABA_CLOUD_SECURITY_TOKEN');
$this->ALGORITHM = 'ACS3-HMAC-SHA256'; // Set the encryption algorithm.
}
/**
* This is a signature example. Replace the sample parameters in the main method with your actual values.
* ROA-style and RPC-style APIs differ only in how the canonicalUri value is obtained. The rest of the process is the same.
*
* Obtain the request method (methods), request parameter name (name), request parameter type (type), and request parameter position (in) from the API metadata, and then encapsulate the parameters into SignatureRequest.
* 1. If a request parameter is marked "in":"query" in the metadata, pass it in queryParam. Note: For an RPC-style API, this type of parameter can also be passed in the body, with content-type set to application/x-www-form-urlencoded. See example 3.
* 2. If a request parameter is marked "in": "body" in the metadata, pass it in the body, with the MIME type set to application/octet-stream or application/json. For an RPC-style API, application/json is not recommended. Use example 3 instead.
* 3. If a request parameter is marked "in": "formData" in the metadata, pass it in the body, with the MIME type set to application/x-www-form-urlencoded.
*/
public function main()
{
// RPC-style API request example 1: the request parameter is "in":"query".
$request = $this->createRequest('POST', '/', 'ecs.cn-hangzhou.aliyuncs.com', 'DescribeInstanceStatus', '2014-05-26');
// The request parameters of DescribeInstanceStatus are as follows:
$request['queryParam'] = [
// RegionId is displayed as a String in the metadata, "in":"query", and is required.
'RegionId' => 'cn-hangzhou',
// InstanceId is displayed as an array in the metadata, "in":"query", and is optional.
'InstanceId' => ["i-bp11ht4h2kdXXXXXXXX", "i-bp16maz3h3xgXXXXXXXX", "i-bp10r67hmslXXXXXXXX"]
];
// // RPC-style API request example 2: the request parameter is "in":"body" (file upload scenario).
// $request = $this->createRequest('POST', '/', 'ocr-api.cn-hangzhou.aliyuncs.com', 'RecognizeGeneral', '2021-07-07');
// // The request parameter is marked "in": "body" in the metadata. Pass it in the body.
// $filePath = 'D:\\test.png';
// // Pass the binary file by using a file resource.
// $fileResource = fopen($filePath, 'rb');
// $request['body'] = stream_get_contents($fileResource);
// $request['headers']['content-type'] = 'application/octet-stream'; // Set Content-Type to application/octet-stream.
// // Close the file resource.
// fclose($fileResource);
// // RPC-style API request example 3: the request parameter is "in": "formData" or "in":"body" (non-file upload scenario).
// $request = $this->createRequest('POST', '/', 'mt.aliyuncs.com', 'TranslateGeneral', '2018-10-12');
// // The request parameters of TranslateGeneral are as follows:
// $request['queryParam'] = [
// // Context is displayed as a String in the metadata, "in":"query", and is optional.
// 'Context' => 'Morning',
// ];
// $formData = [
// 'FormatType' => 'text',
// 'SourceLanguage' => 'zh',
// 'TargetLanguage' => 'en',
// 'SourceText' => 'Hello',
// 'Scene' => 'general',
// ];
// $str = self::formDataToString($formData);
// $request['body'] = $str;
// $request['headers']['content-type'] = 'application/x-www-form-urlencoded';
// // An ROA-style API POST request.
// $request = $this->createRequest('POST', '/clusters', 'cs.cn-beijing.aliyuncs.com', 'CreateCluster', '2015-12-15');
// $bodyData = [
// 'name' => 'test-cluster',
// 'region_id' => 'cn-beijing',
// 'cluster_type' => 'ExternalKubernetes',
// 'vpcid' => 'vpc-2zeou1uod4ylaXXXXXXXX',
// 'service_cidr' => '10.2.0.0/24',
// 'security_group_id' => 'sg-2ze1a0rlgeo7XXXXXXXX',
// "vswitch_ids" => [
// "vsw-2zei30dhfldu8XXXXXXXX"
// ]
// ];
// $request['body'] = json_encode($bodyData, JSON_UNESCAPED_UNICODE);
// $request['headers']['content-type'] = 'application/json; charset=utf-8';
// // An ROA-style API GET request.
// // If canonicalUri contains a path parameter, encode the path parameter by using rawurlencode({path parameter}).
// $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX';
// $canonicalUri = sprintf("/clusters/%s/resources", rawurlencode($cluster_id));
// $request = $this->createRequest('GET', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DescribeClusterResources', '2015-12-15');
// $request['queryParam'] = [
// 'with_addon_resources' => true,
// ];
// // An ROA-style API DELETE request.
// $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX';
// $canonicalUri = sprintf("/clusters/%s", rawurlencode($cluster_id));
// $request = $this->createRequest('DELETE', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DeleteCluster', '2015-12-15');
$this->getAuthorization($request);
// Call the API operation.
$this->callApi($request);
}
private function createRequest($httpMethod, $canonicalUri, $host, $xAcsAction, $xAcsVersion)
{
$headers = [
'host' => $host,
'x-acs-action' => $xAcsAction,
'x-acs-version' => $xAcsVersion,
'x-acs-date' => gmdate('Y-m-d\TH:i:s\Z'),
'x-acs-signature-nonce' => bin2hex(random_bytes(16)),
];
return [
'httpMethod' => $httpMethod,
'canonicalUri' => $canonicalUri,
'host' => $host,
'headers' => $headers,
'queryParam' => [],
'body' => null,
];
}
private function getAuthorization(&$request)
{
$request['queryParam'] = $this->processObject($request['queryParam']);
$canonicalQueryString = $this->buildCanonicalQueryString($request['queryParam']);
$hashedRequestPayload = hash('sha256', $request['body'] ?? '');
$request['headers']['x-acs-content-sha256'] = $hashedRequestPayload;
if($this->SecurityToken){
$request['headers']['x-acs-security-token'] = $this->SecurityToken;
}
$canonicalHeaders = $this->buildCanonicalHeaders($request['headers']);
$signedHeaders = $this->buildSignedHeaders($request['headers']);
$canonicalRequest = implode("\n", [
$request['httpMethod'],
$request['canonicalUri'],
$canonicalQueryString,
$canonicalHeaders,
$signedHeaders,
$hashedRequestPayload,
]);
$hashedCanonicalRequest = hash('sha256', $canonicalRequest);
$stringToSign = "{$this->ALGORITHM}\n$hashedCanonicalRequest";
$signature = strtolower(bin2hex(hash_hmac('sha256', $stringToSign, $this->AccessKeySecret, true)));
$authorization = "{$this->ALGORITHM} Credential={$this->AccessKeyId},SignedHeaders=$signedHeaders,Signature=$signature";
$request['headers']['Authorization'] = $authorization;
}
private function callApi($request)
{
try {
// Send the request by using cURL.
$url = "https://" . $request['host'] . $request['canonicalUri'];
// Add the request parameters to the URL.
if (!empty($request['queryParam'])) {
$url .= '?' . http_build_query($request['queryParam']);
}
echo $url;
// Initialize a cURL session.
$ch = curl_init();
// Set cURL options.
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL certificate verification. Note that this reduces security and must not be used in a production environment. Not recommended.
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return the content instead of printing it.
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->convertHeadersToArray($request['headers'])); // Add the request headers.
// Set cURL options based on the request type.
switch ($request['httpMethod']) {
case "GET":
break;
case "POST":
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request['body']);
break;
case "DELETE":
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
default:
echo "Unsupported HTTP method: " . $request['body'];
throw new Exception("Unsupported HTTP method");
}
// Send the request.
$result = curl_exec($ch);
// Check whether an error occurred.
if (curl_errno($ch)) {
echo "Failed to send request: " . curl_error($ch);
} else {
echo $result;
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
} finally {
// Close the cURL session.
curl_close($ch);
}
}
function formDataToString($formData)
{
$res = self::processObject($formData);
return http_build_query($res);
}
function processObject($value)
{
// If the value is empty, no further processing is required.
if ($value === null) {
return;
}
$tmp = [];
foreach ($value as $k => $v) {
if (0 !== strpos($k, '_')) {
$tmp[$k] = $v;
}
}
return self::flatten($tmp);
}
private static function flatten($items = [], $delimiter = '.', $prepend = '')
{
$flatten = [];
foreach ($items as $key => $value) {
$pos = \is_int($key) ? $key + 1 : $key;
if (\is_object($value)) {
$value = get_object_vars($value);
}
if (\is_array($value) && !empty($value)) {
$flatten = array_merge(
$flatten,
self::flatten($value, $delimiter, $prepend . $pos . $delimiter)
);
} else {
if (\is_bool($value)) {
$value = true === $value ? 'true' : 'false';
}
$flatten["$prepend$pos"] = $value;
}
}
return $flatten;
}
private function convertHeadersToArray($headers)
{
$headerArray = [];
foreach ($headers as $key => $value) {
$headerArray[] = "$key: $value";
}
return $headerArray;
}
private function buildCanonicalQueryString($queryParams)
{
ksort($queryParams);
// Build and encode query parameters
$params = [];
foreach ($queryParams as $k => $v) {
if (null === $v) {
continue;
}
$str = rawurlencode($k);
if ('' !== $v && null !== $v) {
$str .= '=' . rawurlencode($v);
} else {
$str .= '=';
}
$params[] = $str;
}
return implode('&', $params);
}
private function buildCanonicalHeaders($headers)
{
// Sort headers by key and concatenate them
uksort($headers, 'strcasecmp');
$canonicalHeaders = '';
foreach ($headers as $key => $value) {
$canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n";
}
return $canonicalHeaders;
}
private function buildSignedHeaders($headers)
{
// Build the signed headers string
$signedHeaders = array_keys($headers);
sort($signedHeaders, SORT_STRING | SORT_FLAG_CASE);
return implode(';', array_map('strtolower', $signedHeaders));
}
}
$demo = new SignatureDemo();
$demo->main();
.NET
L'exemple de code s'exécute avec .NET 8.0.302. Vous devrez peut-être adapter le code en fonction de votre environnement réel.
using System.Globalization;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Newtonsoft.Json;
namespace SignatureDemo
{
public class Request
{
public string HttpMethod { get; private set; }
public string CanonicalUri { get; private set; }
public string Host { get; private set; }
public string XAcsAction { get; private set; }
public string XAcsVersion { get; private set; }
public SortedDictionary<string, object> Headers { get; private set; }
public byte[]? Body { get; set; }
public Dictionary<string, object> QueryParam { get; set; }
public Request(string httpMethod, string canonicalUri, string host, string xAcsAction, string xAcsVersion)
{
HttpMethod = httpMethod;
CanonicalUri = canonicalUri;
Host = host;
XAcsAction = xAcsAction;
XAcsVersion = xAcsVersion;
Headers = [];
QueryParam = [];
Body = null;
InitHeader();
}
private void InitHeader()
{
Headers["host"] = Host;
Headers["x-acs-action"] = XAcsAction;
Headers["x-acs-version"] = XAcsVersion;
DateTime utcNow = DateTime.UtcNow;
Headers["x-acs-date"] = utcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture);
Headers["x-acs-signature-nonce"] = Guid.NewGuid().ToString();
}
}
public class Program
{
private static readonly string AccessKeyId = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_ID environment variable is not set.");
private static readonly string AccessKeySecret = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable is not set.");
private static readonly string? SecurityToken = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_SECURITY_TOKEN");
private const string Algorithm = "ACS3-HMAC-SHA256";
private const string ContentType = "content-type";
/**
* This is a signature example. Replace the sample parameters in the main method with your actual values.
* ROA-style and RPC-style APIs differ only in how the canonicalUri value is obtained. The rest of the process is the same.
*
* Obtain the request method (methods), request parameter name (name), request parameter type (type), and request parameter position (in) from the API metadata, and then encapsulate the parameters into SignatureRequest.
* 1. If a request parameter is marked "in":"query" in the metadata, pass it in queryParam. Note: For an RPC-style API, this type of parameter can also be passed in the body, with content-type set to application/x-www-form-urlencoded. See example 3.
* 2. If a request parameter is marked "in": "body" in the metadata, pass it in the body, with the MIME type set to application/octet-stream or application/json. For an RPC-style API, application/json is not recommended. Use example 3 instead.
* 3. If a request parameter is marked "in": "formData" in the metadata, pass it in the body, with the MIME type set to application/x-www-form-urlencoded.
*/
public static void Main(string[] args)
{
// RPC-style API request example 1: the request parameter is "in":"query".
string httpMethod = "POST"; // The request method. Most RPC-style APIs support both POST and GET. POST is used in this example.
string canonicalUri = "/"; // An RPC-style API has no resource path. Therefore, a forward slash (/) is used as the CanonicalURI.
string host = "ecs.cn-hangzhou.aliyuncs.com"; // The service endpoint of the Alibaba Cloud service.
string xAcsAction = "DescribeInstanceStatus"; // The API name.
string xAcsVersion = "2014-05-26"; // The API version.
var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// The request parameters of DescribeInstanceStatus are as follows:
// RegionId is displayed as a String in the metadata, "in":"query", and is required.
request.QueryParam["RegionId"] = "cn-hangzhou";
// InstanceId is displayed as an array in the metadata, "in":"query", and is optional.
List<string> instanceIds = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"];
request.QueryParam["InstanceId"] = instanceIds;
// // RPC-style API request example 2: the request parameter is "in":"body" (file upload scenario).
// string httpMethod = "POST";
// string canonicalUri = "/";
// string host = "ocr-api.cn-hangzhou.aliyuncs.com";
// string xAcsAction = "RecognizeGeneral";
// string xAcsVersion = "2021-07-07";
// var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// // The request parameter is marked "in": "body" in the metadata. Pass it in the body.
// request.Body = File.ReadAllBytes(@"D:\test.png");
// request.Headers["content-type"] = "application/octet-stream";
// // RPC-style API request example 3: the request parameter is "in": "formData" or "in":"body" (non-file upload scenario).
// string httpMethod = "POST";
// string canonicalUri = "/";
// string host = "mt.aliyuncs.com";
// string xAcsAction = "TranslateGeneral";
// string xAcsVersion = "2018-10-12";
// var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// // The request parameters of TranslateGeneral are as follows:
// // Context is displayed as a String in the metadata, "in":"query", and is optional.
// request.QueryParam["Context"] = "Morning";
// // Parameters such as FormatType, SourceLanguage, and TargetLanguage are marked "in":"formData" in the metadata.
// var body = new Dictionary<string, object>
// {
// { "FormatType", "text" },
// { "SourceLanguage", "zh" },
// { "TargetLanguage", "en" },
// { "SourceText", "Hello" },
// { "Scene", "general" },
// };
// var str = FormDataToString(body);
// request.Body = Encoding.UTF8.GetBytes(str);
// request.Headers[ContentType] = "application/x-www-form-urlencoded";
// // An ROA-style API POST request.
// String httpMethod = "POST";
// String canonicalUri = "/clusters";
// String host = "cs.cn-beijing.aliyuncs.com";
// String xAcsAction = "CreateCluster";
// String xAcsVersion = "2015-12-15";
// Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// // The request body. Use JsonConvert to convert the body into a JSON string.
// var body = new SortedDictionary<string, object>
// {
// { "name", "testDemo" },
// { "region_id", "cn-beijing" },
// { "cluster_type", "ExternalKubernetes" },
// { "vpcid", "vpc-2zeou1uod4ylaXXXXXXXX" },
// { "container_cidr", "10.0.0.0/8" },
// { "service_cidr", "172.16.1.0/20" },
// { "security_group_id", "sg-2ze1a0rlgeo7XXXXXXXX" },
// { "vswitch_ids", new List<string>{"vsw-2zei30dhfldu8XXXXXXXX"} },
// };
// string jsonBody = JsonConvert.SerializeObject(body, Formatting.None);
// request.Body = Encoding.UTF8.GetBytes(jsonBody);
// request.Headers[ContentType] = "application/json; charset=utf-8";
// // An ROA-style API GET request.
// String httpMethod = "GET";
// // If canonicalUri contains a path parameter, encode the path parameter by using percentCode({path parameter}).
// String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX") + "/resources";
// String host = "cs.cn-beijing.aliyuncs.com";
// String xAcsAction = "DescribeClusterResources";
// String xAcsVersion = "2015-12-15";
// Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
// request.QueryParam["with_addon_resources"]=true;
// // An ROA-style API DELETE request.
// String httpMethod = "DELETE";
// // If canonicalUri contains a path parameter, encode the path parameter by using percentCode({path parameter}).
// String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX");
// String host = "cs.cn-beijing.aliyuncs.com";
// String xAcsAction = "DeleteCluster";
// String xAcsVersion = "2015-12-15";
// Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
GetAuthorization(request);
// Call the API operation.
var result = CallApiAsync(request);
Console.WriteLine($"result:{result.Result}");
}
private static async Task<string?> CallApiAsync(Request request)
{
try
{
// Declare httpClient.
using var httpClient = new HttpClient();
// Construct the URL.
string url = $"https://{request.Host}{request.CanonicalUri}";
var uriBuilder = new UriBuilder(url);
var query = new List<string>();
// Add the request parameters.
foreach (var entry in request.QueryParam.OrderBy(e => e.Key.ToLower()))
{
string value = entry.Value?.ToString() ?? "";
query.Add($"{entry.Key}={Uri.EscapeDataString(value)}");
}
uriBuilder.Query = string.Join("&", query);
Console.WriteLine(uriBuilder.Uri);
var requestMessage = new HttpRequestMessage
{
Method = new HttpMethod(request.HttpMethod),
RequestUri = uriBuilder.Uri,
};
// Set the request headers.
foreach (var entry in request.Headers)
{
if (entry.Key == "Authorization")
{
requestMessage.Headers.TryAddWithoutValidation("Authorization", entry.Value.ToString()); ;
}
else if (entry.Key == ContentType) // Must be consistent with the value defined in main.
{
continue;
}
else
{
requestMessage.Headers.Add(entry.Key, entry.Value.ToString());
}
}
if (request.Body != null)
{
HttpContent content = new ByteArrayContent(request.Body);
string contentType = request.Headers["content-type"].ToString();
content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
requestMessage.Content = content;
}
// Send the request.
HttpResponseMessage response = await httpClient.SendAsync(requestMessage);
// Read the response content.
string result = await response.Content.ReadAsStringAsync();
return result;
}
catch (UriFormatException e)
{
Console.WriteLine("Invalid URI syntax");
Console.WriteLine(e.Message);
return null;
}
catch (Exception e)
{
Console.WriteLine("Failed to send request");
Console.WriteLine(e);
return null;
}
}
private static void GetAuthorization(Request request)
{
try
{
// Flatten the query parameters whose values are of the List or Map type in queryParam.
request.QueryParam = FlattenDictionary(request.QueryParam);
// Step 1: Construct the canonical request.
StringBuilder canonicalQueryString = new();
foreach (var entry in request.QueryParam.OrderBy(e => e.Key.ToLower()))
{
if (canonicalQueryString.Length > 0)
{
canonicalQueryString.Append('&');
}
canonicalQueryString.Append($"{PercentCode(entry.Key)}={PercentCode(entry.Value?.ToString() ?? "")}");
}
byte[] requestPayload = request.Body ?? Encoding.UTF8.GetBytes("");
string hashedRequestPayload = Sha256Hash(requestPayload);
request.Headers["x-acs-content-sha256"] = hashedRequestPayload;
if (!string.IsNullOrEmpty(SecurityToken))
{
request.Headers["x-acs-security-token"] = SecurityToken;
}
StringBuilder canonicalHeaders = new();
StringBuilder signedHeadersSb = new();
foreach (var entry in request.Headers.OrderBy(e => e.Key.ToLower()))
{
if (entry.Key.StartsWith("x-acs-", StringComparison.CurrentCultureIgnoreCase) || entry.Key.Equals("host", StringComparison.OrdinalIgnoreCase) || entry.Key.Equals(ContentType, StringComparison.OrdinalIgnoreCase))
{
string lowerKey = entry.Key.ToLower();
string value = (entry.Value?.ToString() ?? "").Trim();
canonicalHeaders.Append($"{lowerKey}:{value}\n");
signedHeadersSb.Append($"{lowerKey};");
}
}
string signedHeaders = signedHeadersSb.ToString().TrimEnd(';');
string canonicalRequest = $"{request.HttpMethod}\n{request.CanonicalUri}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{hashedRequestPayload}";
Console.WriteLine($"canonicalRequest:{canonicalRequest}");
// Step 2: Construct the string-to-sign.
string hashedCanonicalRequest = Sha256Hash(Encoding.UTF8.GetBytes(canonicalRequest));
string stringToSign = $"{Algorithm}\n{hashedCanonicalRequest}";
Console.WriteLine($"stringToSign:{stringToSign}");
// Step 3: Calculate the signature.
string signature = HmacSha256(AccessKeySecret, stringToSign);
// Step 4: Construct the Authorization header.
string authorization = $"{Algorithm} Credential={AccessKeyId},SignedHeaders={signedHeaders},Signature={signature}";
request.Headers["Authorization"] = authorization;
Console.WriteLine($"authorization:{authorization}");
}
catch (Exception ex)
{
Console.WriteLine("Failed to get authorization");
Console.WriteLine(ex.Message);
}
}
private static string FormDataToString(Dictionary<string, object> formData)
{
Dictionary<string, object> tileMap = FlattenDictionary( formData);
StringBuilder result = new StringBuilder();
bool first = true;
string symbol = "&";
foreach (var entry in tileMap)
{
string value = entry.Value?.ToString() ?? "";
if (!string.IsNullOrEmpty(value))
{
if (!first)
{
result.Append(symbol);
}
first = false;
result.Append(PercentCode(entry.Key));
result.Append("=");
result.Append(PercentCode(value));
}
}
return result.ToString();
}
private static Dictionary<string, object> FlattenDictionary(Dictionary<string, object> dictionary, string prefix = "")
{
var result = new Dictionary<string, object>();
foreach (var kvp in dictionary)
{
string key = string.IsNullOrEmpty(prefix) ? kvp.Key : $"{prefix}.{kvp.Key}";
if (kvp.Value is Dictionary<string, object> nestedDict)
{
var nestedResult = FlattenDictionary(nestedDict, key);
foreach (var nestedKvp in nestedResult)
{
result[nestedKvp.Key] = nestedKvp.Value;
}
}
else if (kvp.Value is List<string> list)
{
for (int i = 0; i < list.Count; i++)
{
result[$"{key}.{i + 1}"] = list[i];
}
}
else
{
result[key] = kvp.Value;
}
}
return result;
}
private static string HmacSha256(string key, string message)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
byte[] hashMessage = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return BitConverter.ToString(hashMessage).Replace("-", "").ToLower();
}
}
private static string Sha256Hash(byte[] input)
{
byte[] hashBytes = SHA256.HashData(input);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
private static string PercentCode(string str)
{
if (string.IsNullOrEmpty(str))
{
throw new ArgumentException("The input string cannot be null or empty.");
}
return Uri.EscapeDataString(str).Replace("+", "%20").Replace("*", "%2A").Replace("%7E", "~");
}
}
}
Rust
L'exemple de code s'exécute avec rustc 1.82.0. Vous devrez peut-être adapter le code en fonction de votre environnement réel.
Pour exécuter l'exemple Rust, ajoutez les dépendances suivantes au fichier Cargo.toml.
[dependencies]
serde = { version = "1.0" }
serde_json = "1.0"
rand = "0.8"
base64 = "0.21"
sha2 = "0.10"
chrono = "0.4"
hmac = "0.12"
hex = "0.4"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
percent-encoding = "2.1"use core::str;
use std::collections::{BTreeMap, HashMap};
use std::env;
use std::time::{SystemTime, SystemTimeError};
use chrono::DateTime;
use hmac::{Hmac, Mac};
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
use rand::Rng;
use serde_json::{json, Value};
use std::borrow::Cow;
use reqwest::{
Client,
header::{HeaderMap, HeaderValue}, Method, Response, StatusCode,
};
use sha2::{Digest, Sha256};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
// Generate x-acs-date.
pub fn current_timestamp() -> Result<u64, SystemTimeError> {
Ok(SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs())
}
// Perform URL encoding.
pub fn percent_code(encode_str: &str) -> Cow<'_, str> {
let encoded = utf8_percent_encode(encode_str, NON_ALPHANUMERIC)
.to_string()
.replace("+", "20%")
.replace("%5F", "_")
.replace("%2D", "-")
.replace("%2E", ".")
.replace("%7E", "~");
Cow::Owned(encoded) // Return a Cow<str> that can hold a String or an &str.
}
fn flatten_target_ops(
targets: Vec<HashMap<&str, &str>>,
base_key: &str,
) -> Vec<(&'static str, &'static str)> {
let mut result = Vec::new();
for (idx, item) in targets.iter().enumerate() {
let prefix = format!("{}.{}", base_key, idx + 1);
for (&k, &v) in item {
let key = format!("{}.{}", prefix, k);
let key_static: &'static str = Box::leak(key.into_boxed_str());
let value_static: &'static str = Box::leak(v.to_string().into_boxed_str());
result.push((key_static, value_static));
}
}
result
}
/// Calculate the SHA256 hash.
pub fn sha256_hex(message: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(message);
format!("{:x}", hasher.finalize()).to_lowercase()
}
// HMAC SHA256
pub fn hmac256(key: &[u8], message: &str) -> Result<Vec<u8>, String> {
let mut mac = Hmac::<Sha256>::new_from_slice(key)
.map_err(|e| format!("use data key on sha256 fail:{}", e))?;
mac.update(message.as_bytes());
let signature = mac.finalize();
Ok(signature.into_bytes().to_vec())
}
// Generate the signature nonce.
pub fn generate_random_string(length: usize) -> String {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let mut rng = rand::thread_rng();
(0..length)
.map(|_| CHARSET[rng.gen_range(0..CHARSET.len())] as char)
.collect()
}
pub fn generate_nonce() -> String {
generate_random_string(32)
}
// Construct the canonical query parameters (encoded).
pub fn build_sored_encoded_query_string(query_params: &[(&str, &str)]) -> String {
let sorted_query_params: BTreeMap<_, _> = query_params.iter().copied().collect();
let encoded_params: Vec<String> = sorted_query_params
.into_iter()
.map(|(k, v)| {
let encoded_key = percent_code(k);
let encoded_value = percent_code(v);
format!("{}={}", encoded_key, encoded_value)
})
.collect();
encoded_params.join("&")
}
// Read the response.
pub async fn read_response(result: Response) -> Result<(StatusCode, String), String> {
let status = result.status();
let data = result.bytes().await.map_err(|e| format!("Read response body failed: {}", e))?;
let res = match str::from_utf8(&data) {
Ok(s) => s.to_string(),
Err(_) => return Err("Body contains non UTF-8 characters".to_string()),
};
Ok((status, res))
}
// Define the value type of FormData.
#[derive(Debug, Clone)]
pub enum FormValue {
String(String),
Vec(Vec<String>),
HashMap(HashMap<String, String>),
}
// Define an enum for the request body to handle the Json, Binary, and FormData body types in a unified way.
pub enum RequestBody {
Json(HashMap<String, Value>), // Json
Binary(Vec<u8>), // Binary
FormData(HashMap<String, FormValue>), // FormData
None,
}
// Canonicalize the request.
pub async fn call_api(
client: Client,
method: Method,
host: &str,
canonical_uri: &str,
query_params: &[(&str, &str)],
action: &str,
version: &str,
body: RequestBody,
access_key_id: &str,
access_key_secret: &str,
) -> Result<String, String> {
// Process the request body based on the body type and store the result in the body_content variable.
let body_content = match &body {
RequestBody::Json(body_map) => json!(body_map).to_string(),
RequestBody::Binary(binary_data) => {
STANDARD.encode(binary_data)
},
RequestBody::FormData(form_data) => {
let params: Vec<String> = form_data
.iter()
.flat_map(|(k, v)| {
match v {
FormValue::String(s) => {
vec![format!("{}={}", percent_code(k), percent_code(&s))]
},
FormValue::Vec(vec) => {
vec.iter()
.map(|s| format!("{}={}", percent_code(k), percent_code(s)))
.collect::<Vec<_>>()
},
FormValue::HashMap(map) => {
map.iter()
.map(|(sk, sv)| format!("{}={}", percent_code(sk), percent_code(sv)))
.collect::<Vec<_>>()
},
}
})
.collect();
params.join("&")
},
RequestBody::None => String::new(),
};
// Calculate x-acs-content-sha256 of the request body. Prepare x-acs-date, x-acs-signature-nonce, and the headers to be signed.
let hashed_request_payload = if body_content.is_empty() {
sha256_hex("")
} else {
sha256_hex(&body_content)
};
// x-acs-date
let now_time = current_timestamp().map_err(|e| format!("Get current timestamp failed: {}", e))?;
let datetime = DateTime::from_timestamp(now_time as i64, 0).ok_or_else(|| format!("Get datetime from timestamp failed: {}", now_time))?;
let datetime_str = datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string();
// x-acs-signature-nonce
let signature_nonce = generate_nonce();
println!("Signature Nonce: {}", signature_nonce);
// The headers to be signed.
let sign_header_arr = &[
"host",
"x-acs-action",
"x-acs-content-sha256",
"x-acs-date",
"x-acs-signature-nonce",
"x-acs-version",
];
let sign_headers = sign_header_arr.join(";");
// 1. Construct the canonical headers.
let mut headers = HeaderMap::new();
headers.insert("Host", HeaderValue::from_str(host).unwrap());
headers.insert("x-acs-action", HeaderValue::from_str(action).unwrap());
headers.insert("x-acs-version", HeaderValue::from_str(version).unwrap());
headers.insert("x-acs-date", HeaderValue::from_str(&datetime_str).unwrap());
headers.insert("x-acs-signature-nonce", HeaderValue::from_str(&signature_nonce).unwrap());
headers.insert("x-acs-content-sha256", HeaderValue::from_str(&hashed_request_payload).unwrap());
// 2. Construct the headers to be signed.
let canonical_query_string = build_sored_encoded_query_string(query_params); // Encode and concatenate the parameters.
println!("CanonicalQueryString: {}", canonical_query_string);
let canonical_request = format!(
"{}\n{}\n{}\n{}\n\n{}\n{}",
method.as_str(),
canonical_uri,
canonical_query_string,
sign_header_arr.iter().map(|&header| format!("{}:{}", header, headers[header].to_str().unwrap())).collect::<Vec<_>>().join("\n"),
sign_headers,
hashed_request_payload
);
println!("Canonical Request: {}", canonical_request);
// 3. Calculate the SHA-256 hash of the headers to be signed.
let result = sha256_hex(&canonical_request);
// 4. Construct the string-to-sign.
let string_to_sign = format!("ACS3-HMAC-SHA256\n{}", result);
// 5. Calculate the signature.
let signature = hmac256(access_key_secret.as_bytes(), &string_to_sign)?;
let data_sign = hex::encode(&signature);
let auth_data = format!(
"ACS3-HMAC-SHA256 Credential={},SignedHeaders={},Signature={}",
access_key_id, sign_headers, data_sign
);
// 6. Construct the Authorization header.
headers.insert("Authorization", HeaderValue::from_str(&auth_data).unwrap());
// Construct the URL and append the request parameters.
let url: String;
if !query_params.is_empty() {
url = format!("https://{}{}?{}", host, canonical_uri,canonical_query_string);
} else {
url = format!("https://{}{}", host, canonical_uri);
}
// Send the request.
let response = send_request(
&client,
method,
&url,
headers,
query_params,
&body,
&body_content,
)
.await?;
// Read the response.
let (_, res) = read_response(response).await?;
Ok(res)
}
/// Send the request.
async fn send_request(
client: &Client,
method: Method,
url: &str,
headers: HeaderMap,
query_params: &[(&str, &str)], // Receives the query parameters.
body: &RequestBody, // Determines the body data type.
body_content: &str, // Receives the body parameters when the body is not empty: FormData, Json, or Binary.
) -> Result<Response, String> {
let mut request_builder = client.request(method.clone(), url);
// Add the request headers.
for (k, v) in headers.iter() {
request_builder = request_builder.header(k, v.clone());
}
// Add the request body.
match body {
RequestBody::Binary(_) => {
request_builder = request_builder.header("Content-Type", "application/octet-stream");
request_builder = request_builder.body(body_content.to_string()); // Move the value here.
}
RequestBody::Json(_) => {
// If the body is a map and is not empty, convert it into JSON, store it in the body_content variable, and set application/json; charset=utf-8.
if !body_content.is_empty() {
request_builder = request_builder.body(body_content.to_string());
request_builder = request_builder.header("Content-Type", "application/json; charset=utf-8");
}
}
RequestBody::FormData(_) => {
// Process the form-data type and set content-type.
if !body_content.is_empty() {
request_builder = request_builder.header("Content-Type", "application/x-www-form-urlencoded");
request_builder = request_builder.body(body_content.to_string());
}
}
RequestBody::None => {
request_builder = request_builder.body(String::new());
}
}
// Build the request.
let request = request_builder
.build()
.map_err(|e| format!("build request fail: {}", e))?;
// Send the request.
let response = client
.execute(request)
.await
.map_err(|e| format!("execute request fail: {}", e))?;
// Return the result.
Ok(response)
}
/**
*
* This is a signature example. Replace the sample parameters in the main method with your actual values.
* <p>
* Obtain the request method (methods), request parameter name (name), request parameter type (type), and request parameter position (in) from the API metadata.
* 1. If a request parameter is marked "in":"query" in the metadata, pass it in query_params. Note: For an RPC-style API, this type of parameter can also be passed in the body, with content-type set to application/x-www-form-urlencoded. See example 3.
* 2. If a request parameter is marked "in": "body" in the metadata, pass it in the body, with the MIME type set to application/octet-stream or application/json. For an RPC-style API, application/json is not recommended. Use example 3 instead.
* 2. If a request parameter is marked "in": "formData" in the metadata, pass it in the body, with the MIME type set to application/x-www-form-urlencoded.
*/
#[tokio::main]
async fn main() {
// Create the HTTP client.
let client = Client::new();
// env::var() obtains the AccessKey ID and AccessKey secret from environment variables.
let access_key_id = env::var("ALIBABA_CLOUD_ACCESS_KEY_ID").expect("Cannot get access key id.");
let access_key_secret = env::var("ALIBABA_CLOUD_ACCESS_KEY_SECRET").expect("Cannot get access key id.");
let access_key_id: &str = &access_key_id;
let access_key_secret: &str = &access_key_secret;
// RPC-style API request example 1: the request parameter is "in":"query". POST
let method = Method::POST; // The request method.
let host = "ecs.cn-hangzhou.aliyuncs.com"; // endpoint
let canonical_uri = "/"; // An RPC-style API has no resource path. Therefore, a forward slash (/) is used as the CanonicalURI.
let action = "DescribeInstanceStatus"; // The API name.
let version = "2014-05-26"; // The API version.
let region_id = "cn-hangzhou";
let instance_ids = vec![
"i-bp11ht4XXXXXXXX",
"i-bp16mazXXXXXXXX",
];
let mut query: Vec<(&str, &str)> = Vec::new();
query.push(("RegionId", region_id));
for (index, instance_id) in instance_ids.iter().enumerate() {
let key = format!("InstanceId.{}", index + 1);
query.push((Box::leak(key.into_boxed_str()), instance_id));
}
// The query parameters.
let query_params: &[(&str, &str)] = &query;
// When the request body is empty.
let body = RequestBody:: None;
// RPC-style API with "in":"query" where the query parameters are of a complex type. POST
// let method = Method::POST; // The request method.
// let host = "tds.cn-shanghai.aliyuncs.com"; // endpoint
// let canonical_uri = "/"; // An RPC-style API has no resource path. Therefore, a forward slash (/) is used as the CanonicalURI.
// let action = "AddAssetSelectionCriteria"; // The API name.
// let version = "2018-12-03"; // The API version.
// Define the parameters.
// let mut target_op = HashMap::new();
// target_op.insert("Operation", "add");
// target_op.insert("Target", "i-2ze1j7ocdXXXXXXXX");
// Define the TargetOperationList parameter, whose collection contains items of the map type.
// let target_operation_list = vec![target_op];
// Flatten the parameters.
// let mut query = flatten_target_ops(target_operation_list, "TargetOperationList");
// Normal parameters.
// query.push(("SelectionKey", "85a561b7-27d5-47ad-a0ec-XXXXXXXX"));
// let query_params: &[(&str, &str)] = &query;
// let body = RequestBody:: None;
// RPC-style API request example 2: the request parameter is "in":"body" (file upload scenario). POST
// let method = Method::POST; // The request method.
// let host = "ocr-api.cn-hangzhou.aliyuncs.com";
// let canonical_uri = "/";
// let action = "RecognizeGeneral";
// let version = "2021-07-07";
// The request parameter is "in":"body" of the binary file type.
// let binary_data = std::fs::read("<FILE_PATH>").expect("Failed to read the file."); // Replace <FILE_PATH> with the actual file path.
// When the body is of the binary type.
// let body = RequestBody::Binary(binary_data);
// The query parameters are empty.
// let query_params = &[];
// RPC-style API request example 3: the request parameter is "in": "formData" or "in":"body" (non-file upload scenario). POST
// let method = Method::POST; // The request method.
// let host = "mt.aliyuncs.com";
// let canonical_uri = "/";
// let action = "TranslateGeneral";
// let version = "2018-10-12";
// // Parameters such as FormatType, SourceLanguage, and TargetLanguage are marked "in":"formData" in the metadata.
// let mut form_data = HashMap::new(); // The body type is FormData(HashMap<String, FormValue>). FormValue supports Vec<String>, HashSet<String>, HashMap<String, String>, and more. You can add more types to the FormValue enum.
// form_data.insert(String::from("FormatType"),FormValue::String(String::from("text")));
// form_data.insert(String::from("SourceLanguage"),FormValue::String(String::from("zh")));
// form_data.insert(String::from("TargetLanguage"),FormValue::String(String::from("en")));
// form_data.insert(String::from("SourceText"),FormValue::String(String::from("Hello")));
// form_data.insert(String::from("Scene"),FormValue::String(String::from("general")));
// The query parameters.
// let query_params = &[("Context", "Morning")];
// When the body is of the FormData type, "in":"formdata".
// let body = RequestBody::FormData(form_data);
// ROA-style API POST request. API: CreateCluster, which creates a cluster.
// Define the API request constants.
// let method = Method::POST; // The request method.
// let host = "cs.cn-hangzhou.aliyuncs.com";
// let canonical_uri = "/clusters";
// let action = "CreateCluster";
// let version = "2015-12-15";
// Set the request body parameters.
// let mut body_json = HashMap::new(); // The body type is Json(HashMap<String, Value>). Value supports the following types: Value::String("test".to_string()) // String Value::Number(serde_json::Number::from(42)) // Number Value::Bool(true) // Boolean Value::Null // Null Value::Array(vec![Value::from(1), Value::from(2), Value::from(3)]) //Array json!({"nested_key": "nested_value"})
// body_json.insert(String::from("name"),json!("test-cluster"));
// body_json.insert(String::from("region_id"),json!("cn-hangzhou"));
// body_json.insert(String::from("cluster_type"),json!("ExternalKubernetes"));
// body_json.insert(String::from("vpcid"),json!("vpc-2zeou1uodXXXXXXXX"));
// body_json.insert(String::from("container_cidr"),json!("10.X.X.X/X"));
// body_json.insert(String::from("service_cidr"),json!("10.X.X.X/X"));
// body_json.insert(String::from("security_group_id"),json!("sg-2ze1a0rlgXXXXXXXX"));
// body_json.insert(
// String::from("vswitch_ids"),
// Value::Array(vec![
// Value::from("vsw-2zei30dhflXXXXXXXX"),
// Value::from("vsw-2zei30dhflXXXXXXXX"),
// Value::from("vsw-2zei30dhflXXXXXXXX"),
// ]),
// );
// The query parameters are empty.
// let query_params = &[];
// When the body is of the Json type.
// let body = RequestBody::Json(body_json);
// ROA-style API GET request. API: DescribeClusterResources, which queries the resources associated with a specified cluster.
// let method = Method::GET; // The request method.
// let host = "cs.cn-hangzhou.aliyuncs.com"; // endpoint
// // Concatenate the resource path.
// let uri = format!("/clusters/{}/resources", percent_code("ce196d21571a64be9XXXXXXXX").as_ref());
// let canonical_uri = uri.as_str(); // Convert the resource path into the &str type.
// let action = "DescribeClusterResources"; // The API name.
// let version = "2015-12-15"; // The API version.
// // Set the query parameters.
// let query_params = &[("with_addon_resources", if true { "true" } else { "false" })]; // "true" or "false"
// // Set the body parameters to empty.
// let body = RequestBody:: None;
// ROA-style API DELETE request. API: DeleteCluster, which deletes a pay-as-you-go cluster.
// let method = Method::DELETE;
// let host = "cs.cn-hangzhou.aliyuncs.com";
// let uri = format!("/clusters/{}", percent_code("ce0138ff31ad044f8XXXXXXXX").as_ref());
// let canonical_uri = uri.as_str(); // Convert the resource path into the &str type.
// let action = "DeleteCluster";
// let version = "2015-12-15";
// // The query parameters.
// let query_params = &[];
// // When the body parameters are empty.
// let body = RequestBody:: None;
// The SendSms API operation.
// let method = Method::POST; // The request method.
// let host = "dysmsapi.aliyuncs.com"; // endpoint
// let canonical_uri = "/"; // An RPC-style API has no resource path. Therefore, a forward slash (/) is used as the CanonicalURI.
// let action = "SendSms"; // The API name.
// let version = "2017-05-25"; // The API version.
// let mut query: Vec<(&str, &str)> = Vec::new();
// query.push(("PhoneNumbers", "<YOUR_PHONENUMBERS>"));
// query.push(("TemplateCode", "<YOUR_TEMPLATECODE>"));
// query.push(("SignName", "<YOUR_SIGNNAME>"));
// query.push(("TemplateParam", "<YOUR_TEMPLATEPARAM>"));
// // The query parameters.
// let query_params: &[(&str, &str)] = &query;
// // When the request body is empty.
// let body = RequestBody:: None;
// Send the request.
match call_api(
client.clone(),
method, // The API request method: POST, GET, or DELETE.
host, // The API endpoint.
canonical_uri, // The API resource path.
query_params, // The "in":"query" query parameters.
action, // The API name.
version, // The API version.
body, // The "in":"body" request body parameters. The Json, FormData, and Binary types are supported.
access_key_id,
access_key_secret,
)
.await {
Ok(response) => println!("Response: {}", response),
Err(error) => eprintln!("Error: {}", error),
}
}Shell
#!/bin/bash
accessKey_id="<YOUR-ACCESSKEY-ID>"
accessKey_secret="<YOUR-ACCESSKEY-SECRET>"
algorithm="ACS3-HMAC-SHA256"
# The request parameters. Modify this part based on your actual scenario.
httpMethod="POST"
host="dns.aliyuncs.com"
queryParam=("DomainName=example.com" "RRKeyWord=@")
action="DescribeDomainRecords"
version="2015-01-09"
canonicalURI="/"
# A parameter of the body or formdata type is passed in the body.
# Parameter of the body type: the body value is a JSON string, such as "{'key1':'value1','key2':'value2'}", and you must add content-type:application/json; charset=utf-8 to the signed headers.
# If the parameter of the body type is a binary file: you do not need to modify the body. Add content-type:application/octet-stream to the signed headers and add the --data-binary parameter to curl_command.
# Parameter of the formdata type: the body parameter format is "key1=value1&key2=value2", and you must add content-type:application/x-www-form-urlencoded to the signed headers.
body=""
# The UTC time in the ISO 8601 standard.
utc_timestamp=$(date +%s)
utc_date=$(date -u -d @${utc_timestamp} +"%Y-%m-%dT%H:%M:%SZ")
# The x-acs-signature-nonce random number.
random=$(uuidgen | sed 's/-//g')
# The signed headers.
headers="host:${host}
x-acs-action:${action}
x-acs-version:${version}
x-acs-date:${utc_date}
x-acs-signature-nonce:${random}"
# The URL encoding function.
urlencode() {
local string="${1}"
local strlen=${#string}
local encoded=""
local pos c o
for (( pos=0 ; pos<strlen ; pos++ )); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="${c}" ;;
* ) printf -v o '%%%02X' "'$c"
esac
encoded+="${o}"
done
echo "${encoded}"
}
# Step 1: Construct the canonical request.
# Flatten all parameters in queryParam.
newQueryParam=()
# Traverse each original parameter.
for param in "${queryParam[@]}"; do
# Check whether the parameter contains an equal sign to determine whether it is a key-value pair.
if [[ "$param" == *"="* ]]; then
# Split the key and the value.
IFS='=' read -r key value <<< "$param"
# URL-encode the value.
value=$(urlencode "$value")
# Check whether the value is a list by looking for parentheses.
if [[ "$value" =~ ^\(.+\)$ ]]; then
# Remove the parentheses on both sides.
value="${value:1:-1}"
# Split the value list by using IFS.
IFS=' ' read -ra values <<< "$value"
# Add an index for each value.
index=1
for val in "${values[@]}"; do
# Remove the double quotation marks.
val="${val%\"}"
val="${val#\"}"
# Add the value to the new array.
newQueryParam+=("$key.$index=$val")
((index++))
done
else
# If the value is not a list, add it directly.
newQueryParam+=("$key=$value")
fi
else
# If no equal sign exists, keep the parameter as it is.
newQueryParam+=("$param")
fi
done
# Process and sort the new query parameters.
sortedParams=()
declare -A paramsMap
for param in "${newQueryParam[@]}"; do
IFS='=' read -r key value <<< "$param"
paramsMap["$key"]="$value"
done
# Sort the parameters by key.
for key in $(echo ${!paramsMap[@]} | tr ' ' '\n' | LC_ALL=C sort); do
sortedParams+=("$key=${paramsMap[$key]}")
done
# 1.1 Construct the canonical query string.
canonicalQueryString=""
first=true
for item in "${sortedParams[@]}"; do
[ "$first" = true ] && first=false || canonicalQueryString+="&"
# Check whether an equal sign exists.
if [[ "$item" == *=* ]]; then
canonicalQueryString+="$item"
else
canonicalQueryString+="$item="
fi
done
# 1.2 Process the request body.
hashedRequestPayload=$(echo -n "$body" | openssl dgst -sha256 | awk '{print $2}')
headers="${headers}
x-acs-content-sha256:$hashedRequestPayload"
# 1.3 Construct the canonical headers.
canonicalHeaders=$(echo "$headers" | grep -E '^(host|content-type|x-acs-)' | while read line; do
key=$(echo "$line" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')
value=$(echo "$line" | cut -d':' -f2-)
echo "${key}:${value}"
done | sort | tr '\n' '\n')
signedHeaders=$(echo "$headers" | grep -E '^(host|content-type|x-acs-)' | while read line; do
key=$(echo "$line" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')
echo "$key"
done | sort | tr '\n' ';' | sed 's/;$//')
# 1.4 Construct the canonical request.
canonicalRequest="${httpMethod}\n${canonicalURI}\n${canonicalQueryString}\n${canonicalHeaders}\n\n${signedHeaders}\n${hashedRequestPayload}"
echo -e "canonicalRequest=${canonicalRequest}"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"
str=$(echo "$canonicalRequest" | sed 's/%/%%/g')
hashedCanonicalRequest=$(printf "${str}" | openssl sha256 -hex | awk '{print $2}')
# Step 2: Construct the string-to-sign.
stringToSign="${algorithm}\n${hashedCanonicalRequest}"
echo -e "stringToSign=$stringToSign"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"
# Step 3: Calculate the signature.
signature=$(printf "${stringToSign}" | openssl dgst -sha256 -hmac "${accessKey_secret}" | sed 's/^.* //')
echo -e "signature=${signature}"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++++"
# Step 4: Construct the Authorization header.
authorization="${algorithm} Credential=${accessKey_id},SignedHeaders=${signedHeaders},Signature=${signature}"
echo -e "authorization=${authorization}"
# Construct the curl command.
url="https://$host$canonicalURI"
curl_command="curl -X $httpMethod '$url?$canonicalQueryString'"
# Add the request headers.
IFS=$'\n' # Set the line feed as the new IFS.
for header in $headers; do
curl_command="$curl_command -H '$header'"
done
curl_command+=" -H 'Authorization:$authorization'"
# If the parameter of the body type is a binary file, comment out the following line of code.
curl_command+=" -d '$body'"
# If the parameter of the body type is a binary file, uncomment the following line of code.
#curl_command+=" --data-binary @"/root/001.png" "
echo "$curl_command"
# Run the curl command.
eval "$curl_command"
Langage C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include <stdint.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include <curl/curl.h>
// getenv() obtains the AccessKey ID and AccessKey secret from environment variables.
#define ACCESS_KEY_ID getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
#define ACCESS_KEY_SECRET getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
#define ALGORITHM "ACS3-HMAC-SHA256"
#define BUFFER_SIZE 4096
// The struct that is used for sorting.
typedef struct {
char key[256];
char value[256];
} KeyValuePair;
// The comparison function, which sorts keys in lexicographic order.
int compare_pairs(const void *a, const void *b) {
return strcmp(((const KeyValuePair *)a)->key, ((const KeyValuePair *)b)->key);
}
// URL encoding.
char* percentEncode(const char* str) {
if (str == NULL) {
fprintf(stderr, "The input string cannot be null\n");
return NULL;
}
size_t len = strlen(str);
char* encoded = (char*)malloc(len * 3 + 1);
if (encoded == NULL) {
fprintf(stderr, "Failed to allocate memory\n");
free(encoded);
return NULL;
}
char* ptr = encoded;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)str[i];
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
*ptr++ = c;
} else {
ptr += sprintf(ptr, "%%%02X", c);
}
}
*ptr = '\0';
char* finalEncoded = malloc(strlen(encoded) + 1);
if (finalEncoded) {
char* fptr = finalEncoded;
for (size_t j = 0; j < strlen(encoded); j++) {
if (encoded[j] == '+') {
strcpy(fptr, "%20");
fptr += 3;
} else if (encoded[j] == '*') {
strcpy(fptr, "%2A");
fptr += 3;
} else if (encoded[j] == '~') {
*fptr++ = '~';
} else {
*fptr++ = encoded[j];
}
}
*fptr = '\0';
}
free(encoded);
return finalEncoded;
}
/**
* @brief URL-encodes the query parameters, sorts them in lexicographic order, and generates the canonical query string.
* @param query_params The original query parameter string, in the "key1=value1&key2=value2" format.
* @return char* The sorted and encoded canonical query string. The caller must free the memory.
*/
char* generate_sorted_encoded_query(const char* query_params) {
if (query_params == NULL || strlen(query_params) == 0) {
return strdup(""); // Return an empty string if the parameter is empty.
}
KeyValuePair pairs[100]; // Up to 100 key-value pairs are supported.
int pair_count = 0;
char* copy = strdup(query_params);
if (!copy) {
fprintf(stderr, "Failed to allocate memory\n");
return NULL;
}
char* token = NULL;
char* saveptr = NULL;
token = strtok_r(copy, "&", &saveptr);
while (token != NULL && pair_count < 100) {
char* eq = strchr(token, '=');
if (eq) {
size_t key_len = eq - token;
char key[256], value[256];
strncpy(key, token, key_len);
key[key_len] = '\0';
const char* val = eq + 1;
strncpy(value, val, sizeof(value) - 1);
value[sizeof(value) - 1] = '\0';
char* encoded_key = percentEncode(key);
char* encoded_value = percentEncode(value);
strncpy(pairs[pair_count].key, encoded_key, sizeof(pairs[pair_count].key));
strncpy(pairs[pair_count].value, encoded_value, sizeof(pairs[pair_count].value));
pair_count++;
free(encoded_key);
free(encoded_value);
}
token = strtok_r(NULL, "&", &saveptr);
}
free(copy);
// Sort by key.
qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);
// Concatenate the sorted query string.
char* query_sorted = malloc(BUFFER_SIZE);
if (!query_sorted) {
fprintf(stderr, "Failed to allocate memory\n");
return NULL;
}
query_sorted[0] = '\0';
for (int i = 0; i < pair_count; ++i) {
if (i == 0) {
snprintf(query_sorted, BUFFER_SIZE, "%s=%s", pairs[i].key, pairs[i].value);
} else {
char temp[512];
snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value);
strncat(query_sorted, temp, BUFFER_SIZE - strlen(query_sorted) - 1);
}
}
return query_sorted;
}
// HMAC-SHA256 calculation.
void hmac256(const char *key, const char *message, char *output) {
unsigned char hmac[SHA256_DIGEST_LENGTH];
unsigned int result_len;
HMAC(EVP_sha256(), key, strlen(key), (unsigned char *)message, strlen(message), hmac, &result_len);
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
sprintf(output + (i * 2), "%02x", hmac[i]);
}
output[SHA256_DIGEST_LENGTH * 2] = '\0';
}
// Calculate the SHA-256 hash.
void sha256_hex(const char *input, char *output) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char *)input, strlen(input), hash);
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
sprintf(output + (i * 2), "%02x", hash[i]);
}
output[SHA256_DIGEST_LENGTH * 2] = '\0';
}
// Used to generate x-acs-signature-nonce.
void generate_uuid(char *uuid, size_t size) {
if (size < 37) {
fprintf(stderr, "Buffer size too small for UUID\n");
return;
}
unsigned char random_bytes[16];
RAND_bytes(random_bytes, sizeof(random_bytes));
random_bytes[6] &= 0x0f; // Keep the high 4 bits.
random_bytes[6] |= 0x40; // Set the version to 4.
random_bytes[8] &= 0x3f; // Keep the high 2 bits.
random_bytes[8] |= 0x80; // Set the variant to 10xx.
snprintf(uuid, size,
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
random_bytes[0], random_bytes[1], random_bytes[2], random_bytes[3],
random_bytes[4], random_bytes[5], random_bytes[6], random_bytes[7],
random_bytes[8], random_bytes[9], random_bytes[10], random_bytes[11],
random_bytes[12], random_bytes[13], random_bytes[14], random_bytes[15]);
}
// Upload a file.
size_t read_file(const char *file_path, char **buffer) {
FILE *file = fopen(file_path, "rb");
if (!file) {
fprintf(stderr, "Cannot open file %s\n", file_path);
return 0; // Failed to read the file.
}
fseek(file, 0, SEEK_END);
size_t file_size = ftell(file);
fseek(file, 0, SEEK_SET);
*buffer = (char *)malloc(file_size);
if (!*buffer) {
fprintf(stderr, "Failed to allocate memory for file buffer\n");
fclose(file);
return 0; // Failed to read the file.
}
fread(*buffer, 1, file_size, file);
fclose(file);
return file_size; // Return the number of bytes that are read.
}
// Calculate the Authorization header.
char* get_authorization(const char *http_method, const char *canonical_uri, const char *host,
const char *x_acs_action, const char *x_acs_version, const char *query_params,
const char *body, char *authorization_header,
char *hashed_payload, char *x_acs_date, char *uuid) {
// Prepare x-acs-signature-nonce, x-acs-date, x-acs-content-sha256, and the string-to-sign.
generate_uuid(uuid, 37);
// The format of x-acs-date is yyyy-MM-ddTHH:mm:ssZ, for example, 2025-04-17T07:19:10Z.
time_t now = time(NULL);
struct tm *utc_time = gmtime(&now);
strftime(x_acs_date, 64, "%Y-%m-%dT%H:%M:%SZ", utc_time);
// The string-to-sign.
char signed_headers[] = "host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version";
// x-acs-content-sha256
sha256_hex(body ? body : "", hashed_payload);
printf("Generated x-acs-content-sha256: %s\n", hashed_payload);
// 1. Construct the canonical headers.
char canonical_headers[BUFFER_SIZE];
snprintf(canonical_headers, sizeof(canonical_headers),
"host:%s\nx-acs-action:%s\nx-acs-content-sha256:%s\nx-acs-date:%s\nx-acs-signature-nonce:%s\nx-acs-version:%s",
host, x_acs_action, hashed_payload, x_acs_date, uuid, x_acs_version);
printf("Canonical Headers:\n%s\n", canonical_headers);
// 2. Construct the headers to be signed.
// Sort and encode the query parameters.
char* sorted_query_params = generate_sorted_encoded_query(query_params);
if (!sorted_query_params) {
fprintf(stderr, "Failed to generate the sorted query string\n");
return NULL;
}
char canonical_request[BUFFER_SIZE];
snprintf(canonical_request, sizeof(canonical_request),
"%s\n%s\n%s\n%s\n\n%s\n%s",
http_method,
canonical_uri,
sorted_query_params ? sorted_query_params : "",
canonical_headers,
signed_headers,
hashed_payload);
printf("Canonical Request:\n%s\n", canonical_request);
// 3. Calculate the SHA-256 hash of the canonical request.
char hashed_canonical_request[SHA256_DIGEST_LENGTH * 2 + 1];
sha256_hex(canonical_request, hashed_canonical_request);
printf("hashedCanonicalRequest: %s\n", hashed_canonical_request);
// 4. Construct the string-to-sign.
char string_to_sign[BUFFER_SIZE];
snprintf(string_to_sign, sizeof(string_to_sign), "%s\n%s", ALGORITHM, hashed_canonical_request);
printf("stringToSign:\n%s\n", string_to_sign);
// 5. Calculate the signature.
char signature[SHA256_DIGEST_LENGTH * 2 + 1];
hmac256(ACCESS_KEY_SECRET, string_to_sign, signature);
printf("Signature: %s\n", signature);
// 6. Construct the Authorization header.
snprintf(authorization_header, BUFFER_SIZE,
"%s Credential=%s,SignedHeaders=%s,Signature=%s",
ALGORITHM, ACCESS_KEY_ID, signed_headers, signature);
printf("Authorization: %s\n", authorization_header);
return sorted_query_params;
}
// Send the request.
void call_api(const char *http_method, const char *canonical_uri, const char *host,
const char *x_acs_action, const char *x_acs_version, const char *query_params,
const char *body,const char *content_type, size_t body_length) {
// Obtain the parameter values that are required for signature calculation.
char authorization_header[BUFFER_SIZE];
char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
char x_acs_date[64];
char uuid[37];
// 1. Initialize curl.
CURL *curl = curl_easy_init();
if (!curl) {
fprintf(stderr, "curl_easy_init() failed\n");
goto cleanup;
}
// 2. Calculate the signature. The sorted and encoded query parameters are returned.
char *signed_query_params = get_authorization(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, authorization_header, hashed_payload, x_acs_date, uuid);
// 3. Add the request parameters.
char url[BUFFER_SIZE];
if (signed_query_params && strlen(signed_query_params) > 0) {
snprintf(url, sizeof(url), "https://%s%s?%s", host, canonical_uri, signed_query_params);
} else {
snprintf(url, sizeof(url), "https://%s%s", host, canonical_uri);
}
printf("Request URL: %s\n", url);
// Free the memory.
if (signed_query_params) {
free(signed_query_params); // Free the memory.
}
// 4. Add the request headers.
struct curl_slist *headers = NULL;
char header_value[BUFFER_SIZE];
snprintf(header_value, sizeof(header_value), "Content-Type: %s", content_type);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "Authorization: %s", authorization_header);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "host: %s", host);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-action: %s", x_acs_action);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-content-sha256: %s", hashed_payload);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-date: %s", x_acs_date);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-signature-nonce: %s", uuid);
headers = curl_slist_append(headers, header_value);
snprintf(header_value, sizeof(header_value), "x-acs-version: %s", x_acs_version);
headers = curl_slist_append(headers, header_value);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method);
curl_easy_setopt(curl, CURLOPT_URL, url);
// Other curl settings: disable SSL verification and enable debugging information.
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
// 5. Add the request body.
if (body) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body_length);
if (strcmp(content_type, "application/octet-stream") == 0) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
} else if (strcmp(content_type, "application/x-www-form-urlencoded") == 0) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
} else if (strcmp(content_type, "application/json; charset=utf-8") == 0) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
}
}
printf("RequestBody:%s\n",body);
// 6. Send the request.
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
goto cleanup;
}
cleanup:
if (headers) curl_slist_free_all(headers);
if (curl) curl_easy_cleanup(curl);
}
/**
*
* This is a signature example. Replace the sample parameters in the main method with your actual values.
* <p>
* Obtain the request method (methods), request parameter name (name), request parameter type (type), and request parameter position (in) from the API metadata.
* 1. If a request parameter is marked "in":"query" in the metadata, pass it in query_params. Note: For an RPC-style API, this type of parameter can also be passed in the body, with content-type set to application/x-www-form-urlencoded. See example 3.
* 2. If a request parameter is marked "in": "body" in the metadata, pass it in the body, with the MIME type set to application/octet-stream or application/json. For an RPC-style API, application/json is not recommended. Use example 3 instead.
* 2. If a request parameter is marked "in": "formData" in the metadata, pass it in the body, with the MIME type set to application/x-www-form-urlencoded.
*/
int main() {
// Set the response format to UTF-8.
SetConsoleOutputCP(CP_UTF8);
srand((unsigned int)time(NULL));
/**
* RPC-style API request example: the request parameter is "in":"query" and the query parameters are of a complex type.
*/
const char *http_method = "POST";
const char *canonical_uri = "/";
const char *host = "tds.cn-shanghai.aliyuncs.com";
const char *x_acs_action = "AddAssetSelectionCriteria";
const char *x_acs_version = "2018-12-03";
// Define the SelectionKey parameter of the string type.
const char *selection_key = "85a561b7-27d5-47ad-a0ec-XXXXXXXX";
// Define the TargetOperationList parameter, which is a collection of target objects. You can add more items.
struct {
const char *operation;
const char *target;
} targetOperation_list[] = {
{"add", "i-2ze1j7ocdg9XXXXXXXX"},
// You can add more items.
// {"add", "i-abc123xyzXXXXX"},
};
int count = sizeof(targetOperation_list) / sizeof(targetOperation_list[0]);
KeyValuePair pairs[100]; // Store the original keys and values.
int pair_count = 0;
for (int i = 0; i < count; ++i) {
char op_key[128], target_key[128];
snprintf(op_key, sizeof(op_key), "TargetOperationList.%d.Operation", i + 1);
snprintf(target_key, sizeof(target_key), "TargetOperationList.%d.Target", i + 1);
strncpy(pairs[pair_count].key, op_key, sizeof(pairs[pair_count].key));
strncpy(pairs[pair_count].value, targetOperation_list[i].operation, sizeof(pairs[pair_count].value));
pair_count++;
strncpy(pairs[pair_count].key, target_key, sizeof(pairs[pair_count].key));
strncpy(pairs[pair_count].value, targetOperation_list[i].target, sizeof(pairs[pair_count].value));
pair_count++;
}
// Add the SelectionKey parameter.
snprintf(pairs[pair_count].key, sizeof(pairs[pair_count].key), "SelectionKey");
snprintf(pairs[pair_count].value, sizeof(pairs[pair_count].value), "%s", selection_key);
pair_count++;
// Sorting and encoding are both performed in get_authorization().
qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs);
// Construct the original query string (unencoded).
char query_params[BUFFER_SIZE] = {0};
for (int i = 0; i < pair_count; ++i) {
if (i == 0) {
snprintf(query_params, sizeof(query_params), "%s=%s", pairs[i].key, pairs[i].value);
} else {
char temp[512];
snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value);
strncat(query_params, temp, sizeof(query_params) - strlen(query_params) - 1);
}
}
const char *body = ""; // The request body is empty.
const char *content_type = "application/json; charset=utf-8";
call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* RPC-style API request example: the request parameter is "in":"query".
*/
// Define the API request parameters.
// const char *http_method = "POST";
// const char *canonical_uri = "/";
// const char *host = "ecs.cn-hangzhou.aliyuncs.com";
// const char *x_acs_action = "DescribeInstanceStatus";
// const char *x_acs_version = "2014-05-26";
// // Define the InstanceId array parameter. InstanceId is optional.
// const char *instance_ids[] = {
// "i-bp11ht4hXXXXXXXX",
// "i-bp16maz3XXXXXXXX"
// };
// // Concatenate the InstanceId array.
// char InstanceId[BUFFER_SIZE];
// snprintf(InstanceId, sizeof(InstanceId),
// "InstanceId.1=%s&InstanceId.2=%s",
// instance_ids[0],
// instance_ids[1]);
// // Define the query parameters. Required parameter: RegionId=cn-hangzhou const char *query_params = "RegionId=cn-hangzhou";
// char query_params[BUFFER_SIZE];
// snprintf(query_params, sizeof(query_params),
// "%s&RegionId=cn-hangzhou", InstanceId);
// const char *body = "";
// const char *content_type = "application/json; charset=utf-8";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* RPC-style API request example: the request parameter is "in":"body" (file upload scenario).
*/
// Declare the pointer that stores the file content that is read.
// char *body = NULL;
// size_t body_length = read_file("<YOUR_FILE_PATH>", &body);
// if (body_length > 0) {
// const char *http_method = "POST";
// const char *canonical_uri = "/";
// const char *host = "ocr-api.cn-hangzhou.aliyuncs.com";
// const char *x_acs_action = "RecognizeGeneral";
// const char *x_acs_version = "2021-07-07";
// const char *query_params = "";
// const char *content_type = "application/octet-stream";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, body_length);
// free(body);
// } else {
// fprintf(stderr, "File read error\n");
// }
/**
* RPC-style API request example: the request parameter is "in": "formData" or "in":"body" (non-file upload scenario).
*/
// const char *http_method = "POST";
// const char *canonical_uri = "/";
// const char *host = "mt.aliyuncs.com";
// const char *x_acs_action = "TranslateGeneral";
// const char *x_acs_version = "2018-10-12";
// char query_params[BUFFER_SIZE];
// snprintf(query_params, sizeof(query_params), "Context=%s", "Morning");
// const char *format_type = "text";
// const char *source_language = "zh";
// const char *target_language = "en";
// const char *source_text = "Hello";
// const char *scene = "general";
// char body[BUFFER_SIZE];
// snprintf(body, sizeof(body),
// "FormatType=%s&SourceLanguage=%s&TargetLanguage=%s&SourceText=%s&Scene=%s",
// percentEncode(format_type), percentEncode(source_language), percentEncode(target_language),
// percentEncode(source_text), percentEncode(scene));
// const char *content_type = "application/x-www-form-urlencoded";
// printf("formdate_body: %s\n", body);
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
// RPC-style API request example 3: the request parameter is "in": "formData".
// const char *http_method = "POST";
// const char *canonical_uri = "/";
// const char *host = "sasti.aliyuncs.com";
// const char *x_acs_action = "AskTextToTextMsg";
// const char *x_acs_version = "2020-05-12";
// // query
// const char *query_params = "";
// // body
// const char *Memory = "false";
// const char *Stream = "true";
// const char *ProductCode = "sddp_pre";
// const char *Feature = "{}";
// const char *Model = "yunsec-llm-latest";
// const char *Type = "Chat";
// const char *TopP = "0.9";
// const char *Temperature = "0.01";
// const char *Prompt = "Who are you";
// const char *Application = "sddp_pre";
// char body[BUFFER_SIZE];
// snprintf(body, sizeof(body),
// "Memory=%s&Stream=%s&ProductCode=%s&Feature=%s&Model=%s&Type=%s&TopP=%s&Temperature=%s&Prompt=%s&Application=%s",
// Memory, Stream, ProductCode, Feature, Model, Type, TopP, Temperature, Prompt, Application);
// const char *content_type = "application/x-www-form-urlencoded";
// printf("formdate_body: %s\n", body);
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* ROA-style API POST request with "in" "body".
*/
// const char *http_method = "POST";
// const char *canonical_uri = "/clusters";
// const char *host = "cs.cn-beijing.aliyuncs.com";
// const char *x_acs_action = "CreateCluster";
// const char *x_acs_version = "2015-12-15";
// const char *query_params = "";
// char body[BUFFER_SIZE];
// snprintf(body, sizeof(body),
// "{\"name\":\"%s\",\"region_id\":\"%s\",\"cluster_type\":\"%s\","
// "\"vpcid\":\"%s\",\"container_cidr\":\"%s\","
// "\"service_cidr\":\"%s\",\"security_group_id\":\"%s\","
// "\"vswitch_ids\":[\"%s\"]}",
// "test-cluster", "cn-beijing", "ExternalKubernetes",
// "vpc-2zeou1uod4yXXXXXXXX", "10.X.X.X/XX",
// "10.X.X.X/XX", "sg-2ze1a0rlgeXXXXXXXX",
// "vsw-2zei30dhflXXXXXXXX");
// const char *content_type = "application/json; charset=utf-8";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* ROA-style API GET request.
*/
// const char *http_method = "GET";
// char canonical_uri[BUFFER_SIZE];
// snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s/resources", percentEncode("cd1f5ba0dbfa144XXXXXXXX"));
// const char *host = "cs.cn-beijing.aliyuncs.com";
// const char *x_acs_action = "DescribeClusterResources";
// const char *x_acs_version = "2015-12-15";
// const char *query_params = "with_addon_resources=true";
// const char *body = "";
// const char *content_type = "";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
/**
* ROA-style API DELETE request.
*/
// const char *http_method = "DELETE";
// char canonical_uri[BUFFER_SIZE];
// snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s", percentEncode("cd1f5ba0dbfa144XXXXXXXX"));
// const char *host = "cs.cn-beijing.aliyuncs.com";
// const char *x_acs_action = "DeleteCluster";
// const char *x_acs_version = "2015-12-15";
// const char *query_params = "";
// const char *body = "";
// const char *content_type = "";
// call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body));
// The variables that store the generated values.
char authorization_header[BUFFER_SIZE];
char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1];
char x_acs_date[64];
char uuid[37];
return 0;
}
FAQ
Échec de la signature avec le message d'erreur « Specified signature does not match our calculation. » ou « The request signature does not conform to Aliyun standards. »
Causes :
La plupart des échecs de signature surviennent lors de la construction de la CanonicalRequest. Les causes courantes sont les suivantes :
L'AccessKey ID ou l'AccessKey secret est incorrectement configuré, ou la paire de clés AccessKey est désactivée ou supprimée.
Un paramètre est transmis au mauvais emplacement. Par exemple, un paramètre de requête est transmis dans le corps de la requête.
Les paramètres de la CanonicalQueryString ne sont pas triés par ordre croissant.
Les en-têtes de requête de la CanonicalHeaders ne sont pas triés par ordre alphabétique en minuscules. La signature V3 exige que les noms des en-têtes de requête soient strictement triés par ordre alphabétique. Par exemple,
content-typevient avanthost, ethostvient avantx-acs-action.Le HashedRequestPayload est calculé incorrectement. Pour une requête POST dont le corps est un objet JSON vide
{}, la valeur de hachage SHA-256 est44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a. N'utilisez pas la valeur de hachage d'une chaîne vide.Les espaces ne sont pas encodés sous la forme
%20.L'encodage URL est effectué une fois de trop. Lors du calcul de la signature, l'encodage URL n'est requis qu'une seule fois, lors du traitement des paramètres de chemin et de la chaîne de requête canonique. Par exemple, la présence d'un grand nombre de caractères
%25dans un message d'erreur indique que le caractère%a été encodé.La clé utilisée pour la signature HMAC-SHA256 est incorrecte. La clé de signature correspond à l'AccessKey secret brut. Ne l'encodez pas en Base64 et n'appliquez aucun autre type d'encodage.Solutions :
Vérifiez si votre résultat de calcul local correspond au résultat attendu décrit dans la section , puis contrôlez les éléments suivants :
Assurez-vous que l'AccessKey ID et l'AccessKey secret sont corrects et que la paire de clés AccessKey est activée. Vous pouvez consulter l'état de la paire de clés sur la page de gestion des AccessKey de la console Resource Access Management (RAM).
Vérifiez si la CanonicalRequest indiquée dans le message d'erreur est identique à celle que vous avez calculée localement. Le message d'erreur renvoyé par le serveur contient les valeurs StringToSign et CanonicalRequest attendues. Comparez ligne par ligne votre résultat de calcul local avec ces valeurs pour identifier les différences. En cas de divergence, examinez les causes courantes mentionnées précédemment ainsi que la description fournie dans la section , puis vérifiez attentivement votre code.
Si la CanonicalRequest est identique, vérifiez si la valeur StringToSign du message d'erreur diffère de celle que vous avez calculée localement. Le format de StringToSign est
ACS3-HMAC-SHA256suivi d'un saut de ligne et de la valeur de hachage SHA-256 de la CanonicalRequest. En cas de différence, l'algorithme de hachage peut être incorrect.Si la StringToSign est identique, l'une des deux causes suivantes peut s'appliquer : l'AccessKey secret est incorrect ou le format de clé utilisé dans le calcul HMAC-SHA256 est erroné. La clé doit correspondre à l'AccessKey secret brut.
Comment utiliser Postman pour effectuer des tests ?
Vous ne pouvez pas appeler directement les API Alibaba Cloud via Postman. Pour tester les opérations d'API avec Postman, procédez comme suit :
Utilisez du code ou un script pour calculer l'en-tête
Authorizationselon la méthode de signature.Copiez les en-têtes de requête de la CanonicalHeaders dans la section Headers de Postman, puis ajoutez les informations
Authorizationà la section Headers. Exemple :
| Key | Example value |
| host | dysmsapi.aliyuncs.com |
| x-acs-action | SendSms |
| x-acs-content-sha256 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
| x-acs-date | 2025-04-16T07:45:55Z |
| x-acs-signature-nonce | 315484d3-b129-4966-974a-699b7ee56647 |
| x-acs-version | 2017-05-25 |
| Authorization | ACS3-HMAC-SHA256 Credential=testAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=b37aac99faa507472778256374366b7a47ba48adbc484a53ad789db194658a2d |
-
Configurez les paramètres dans Postman en fonction de leur type. L'ordre des paramètres doit correspondre à celui utilisé lors du calcul de la signature.
Pour un paramètre de requête, saisissez-le dans la section Params.
Pour un paramètre de corps, saisissez-le dans la section Body.
Comment obtenir la version de l'API (x-acs-version) ?
Accédez au Portail des développeurs OpenAPI Alibaba Cloud et sélectionnez le service Alibaba Cloud associé à l'opération d'API que vous souhaitez appeler. Cet exemple utilise ECS. Dans la barre de navigation supérieure d'OpenAPI Explorer, cliquez sur Select a product et accédez à la page d'accueil du service pour afficher la version d'API recommandée.
Consultez la version d'API recommandée sur la page d'accueil du service Alibaba Cloud. Par exemple, la version d'API recommandée pour ECS est 2014-05-26.Les onglets Service regions et API overview sont disponibles en bas de page. Utilisez la liste déroulante de version d'API pour basculer vers la version correspondante et afficher les détails.
Message d'erreur « You are not authorized to do this operation. » lors de l'appel d'une API
Cause : L'utilisateur Resource Access Management (RAM) auquel appartient la paire de clés AccessKey utilisée ne dispose pas des autorisations nécessaires pour appeler l'opération d'API.
Solution : Consultez la section code 403, You are not authorized to do this operation. Action: xxxx..
Comment obtenir une paire de clés AccessKey ?
Une paire de clés AccessKey est un identifiant d'accès permanent fourni par Alibaba Cloud à un utilisateur. Elle se compose d'un AccessKey ID et d'un AccessKey secret. Lorsque vous accédez aux ressources Alibaba Cloud en appelant des API, le système effectue une vérification d'identité et valide la requête en se basant sur l'AccessKey ID inclus dans la requête et sur la signature générée à l'aide de l'AccessKey secret. Pour plus d'informations sur l'obtention d'une paire de clés AccessKey, consultez la section Créer une paire de clés AccessKey pour un utilisateur RAM.
Nous contacter
Si vous rencontrez un problème que vous ne parvenez pas à résoudre lors du calcul de la signature, rejoignez le groupe DingTalk 147535001692 et contactez l'ingénieur de permanence.
Ne rejoignez pas ce groupe pour des questions non liées au calcul de signature, car vous risquez de ne pas recevoir de réponse appropriée.