Alibaba Cloud API Gateway utilise JSON Web Token (JWT) pour autoriser l'accès aux API en se basant sur votre propre système d'utilisateurs, ce qui permet de personnaliser les paramètres de sécurité.
1. Authentification basée sur un token
Présentation
De nombreuses API publiques doivent identifier les appelants afin de déterminer s'il convient d'accorder l'accès aux ressources demandées. Un token constitue un mécanisme d'authentification. Avec l'authentification basée sur un token, une application n'a pas besoin de stocker les informations d'authentification ou de session des utilisateurs sur le serveur. Cela permet une autorisation distribuée et sans état pour les applications web, tout en simplifiant la mise à l'échelle des applications.
Flux de travail
API Gateway utilise le plug-in d'authentification JWT pour gérer l'authentification. Le flux de travail est le suivant :
Un client envoie une requête incluant un token à API Gateway.
API Gateway vérifie le token à l'aide de la clé publique configurée dans le plug-in d'authentification JWT. Si le token est valide, API Gateway transmet la requête au service backend.
Le service backend traite la requête et renvoie une réponse.
API Gateway renvoie la réponse du service backend au client.
Tout au long de ce processus, API Gateway utilise l'authentification par token pour autoriser l'accès aux API en se basant sur votre propre système d'utilisateurs. Les sections suivantes décrivent JSON Web Token (JWT), qu'API Gateway utilise pour l'authentification.
JWT
1.1 Présentation
JSON Web Token (JWT) est une norme ouverte basée sur JSON (RFC 7519) permettant de transmettre des claims de manière sécurisée entre les parties dans un environnement d'application web. Un JWT sert de token d'authentification autonome pouvant inclure l'identité de l'utilisateur, ses rôles et ses permissions. Il peut également contenir des claims supplémentaires requis par la logique métier, ce qui fait des JWT une solution idéale pour l'authentification dans les applications distribuées.
1.2 Structure JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Comme le montre l'exemple précédent, un JWT est une chaîne composée de trois parties :
Header
Payload
Signature
Header
Le Header contient deux parties :
Le type du token, qui est JWT.
L'algorithme de signature utilisé.
Un Header complet est un objet JSON, comme indiqué dans l'exemple suivant :
{
'typ': 'JWT',
'alg': 'HS256'
}
Le Header est ensuite encodé en Base64Url pour former la première partie du JWT.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload
Le Payload contient des claims.
iss: Issuer. The principal that issued the token. This claim is a string.
sub: Subject. The principal that is the subject of the token. This value is unique within the scope of the issuer. It is a case-sensitive string with a maximum length of 255 ASCII characters.
aud: Audience. The recipients that the token is intended for. This is a case-sensitive array of strings.
exp: Expiration Time. The time after which the token is considered invalid. This claim is an integer that represents the number of seconds from 1970-01-01T00:00:00Z.
iat: Issued At. The time at which the token was issued. This claim is an integer that represents the number of seconds from 1970-01-01T00:00:00Z.
jti: JWT ID. A unique identifier for the token. This value is unique for each token created by the same issuer and is often a cryptographically random value to prevent collisions. This value adds a random entropy component to the structured token that an attacker cannot obtain, which helps prevent token guessing and replay attacks.
Vous pouvez également ajouter des claims personnalisées requises par votre système d'utilisateurs. Par exemple, vous pouvez ajouter une claim name pour le pseudonyme d'un utilisateur :
{
"sub": "1234567890",
"name": "John Doe"
}
Le Payload est ensuite encodé en Base64Url pour former la deuxième partie du JWT :
JTdCJTBBJTIwJTIwJTIyc3ViJTIyJTNBJTIwJTIyMTIzNDU2Nzg5MCUyMiUyQyUwQSUyMCUyMCUyMm5hbWUlMjIlM0ElMjAlMjJKb2huJTIwRG9lJTIyJTBBJTdE
Signature
Pour créer la Signature, concaténez le Header et le Payload encodés en Base64Url avec un point (.). Ensuite, signez la chaîne résultante à l'aide de l'algorithme spécifié dans le Header et d'une clé privée ($secret). Cela forme la troisième partie du JWT.
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, '$secret');
Concaténez les trois parties avec un point (.) pour créer le JWT complet, comme indiqué dans l'exemple JWT initial.
1.3 Portée et validité de l'autorisation
API Gateway considère qu'un token est autorisé à accéder à toutes les API liées au plug-in JWT au sein d'un groupe d'API. Pour un contrôle d'accès plus fin, votre service backend doit analyser le token et effectuer l'autorisation. API Gateway valide le champ exp du token. Si le token a expiré, API Gateway rejette immédiatement la requête. Vous devez définir une durée d'expiration inférieure à 7 jours.
1.4 Principales caractéristiques des JWT
Par défaut, les JWT ne sont pas chiffrés. N'incluez pas de données sensibles dans un JWT.
Un JWT peut être utilisé à la fois pour l'authentification et l'échange d'informations, réduisant ainsi le nombre de requêtes de base de données sur le serveur. Le principal inconvénient réside dans sa nature sans état : vous ne pouvez pas révoquer un token ni modifier ses permissions avant son expiration. Une fois émis, un JWT reste valide jusqu'à son expiration, sauf si le serveur met en œuvre une logique de révocation personnalisée.
Un JWT contient des informations d'authentification. S'il est divulgué, toute personne obtenant le token acquiert toutes les permissions associées. Pour réduire ce risque, définissez une courte période de validité pour les JWT. Pour les actions à haute sécurité, ré-authentifiez l'utilisateur avant d'accorder l'accès.
Pour réduire le risque de vol, ne transmettez pas les JWT en texte clair via HTTP. Utilisez HTTPS à la place.
2. Protéger les API avec le plug-in JWT
2.1 Générer une paire de clés JSON Web Key (JWK)
2.1.1 Générer une paire de clés en ligne
Vous pouvez visiter https://tools.top/jwt-encode.html pour générer une clé privée et une clé publique destinées à la génération et à la vérification des tokens. La clé privée est utilisée par le service d'autorisation pour émettre des JWT, tandis que la clé publique est configurée dans le plug-in d'authentification JWT pour permettre à API Gateway de vérifier les signatures des requêtes. API Gateway prend en charge l'algorithme RSA SHA256 et une taille de clé de 2048 bits.
Après avoir ouvert le site, sélectionnez l'onglet JWK format. La page génère automatiquement une paire de clés. Cliquez sur Copy Public Key ou Copy Private Key pour obtenir le contenu JWK correspondant.
2.1.2 Générer une paire de clés localement
L'exemple suivant utilise Java. Vous pouvez trouver des outils similaires dans d'autres langages de programmation pour générer des paires de clés. Créez un projet Maven et ajoutez la dépendance suivante :
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.7.0</version>
</dependency>
Utilisez le code suivant pour générer une paire de clés RSA :
RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
rsaJsonWebKey.setKeyId("authServer");
final String publicKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY);
final String privateKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
2.2 Implémenter un service d'émission de tokens
Utilisez la chaîne JSON JWK de clé publique générée en ligne dans la section 2.1 ou la chaîne JSON privateKeyString générée localement comme clé privée pour émettre des tokens. Ces tokens autorisent les utilisateurs de confiance à accéder aux API protégées. Pour plus d'informations, consultez le Exemple de code pour le service d'émission et d'authentification des tokens. La manière dont vous émettez les tokens dépend de vos scénarios métier. Vous pouvez déployer la fonctionnalité d'émission de tokens dans un environnement de production en tant qu'API standard permettant aux visiteurs d'obtenir un token avec un nom d'utilisateur et un mot de passe, ou générer un token localement et le fournir directement à un utilisateur spécifique.
2.3 Configurer le plug-in JWT
Connectez-vous à la console API Gateway.
Dans le volet de navigation de gauche, choisissez .
Sur la page de gestion des plug-ins, cliquez sur Create Plug-in dans le coin supérieur droit.
Sur la page Create Plug-in, définissez Plug-in Name et sélectionnez un Plug-in Type. Le code suivant illustre un exemple de configuration pour un plug-in d'authentification JWT. Pour plus d'informations sur la configuration, consultez le plug-in d'authentification JWT.
---
parameter: X-Token # The parameter from which to retrieve the JWT. This corresponds to an API parameter.
parameterLocation: header # The location from which to read the JWT. This parameter is optional if the API is in mapping mode, but required if the API is in pass-through mode. Valid values: `query` and `header`.
claimParameters: # Claim parameter mapping. API Gateway maps JWT claims to backend parameters.
- claimName: aud # The name of the claim. Public and private claims are supported.
parameterName: X-Aud # The name of the mapped parameter.
location: header # The location of the mapped parameter. Valid values: `query`, `header`, `path`, and `formData`.
- claimName: userId # The name of the claim. Public and private claims are supported.
parameterName: userId # The name of the mapped parameter.
location: query # The location of the mapped parameter. Valid values: `query`, `header`, `path`, and `formData`.
preventJtiReplay: false # Specifies whether to enable anti-replay checks for the `jti` claim. Default value: false.
# The public key of the JSON Web Key (JWK) generated in section 2.1.
jwk:
kty: RSA
e: AQAB
use: sig
kid: uniq_key
alg: RS256
n: qSVxcknOm0uCq5vGsOmaorPDzHUubBmZZ4UXj-9do7w9X1uKFXAnqfto4TepSNuYU2bA_-tzSLAGBsR-BqvT6w9SjxakeiyQpVmexxnDw5WZwpWenUAcYrfSPEoNU-0hAQwFYgqZwJQMN8ptxkd0170PFauwACOx4Hfr-9FPGy8NCoIO4MfLXzJ3mJ7xqgIZp3NIOGXz-GIAbCf13ii7kSStpYqN3L_zzpvXUAos1FJ9IPXRV84tIZpFVh2lmRh0h8ImK-vI42dwlD_hOIzayL1Xno2R0T-d5AwTSdnep7g-Fwu8-sj4cCRWq3bd61Zs2QOJ8iustH0vSRMYdP5oYQ
2.4 Lier le plug-in JWT à une API
Sur la page Plug-ins, recherchez le plug-in d'authentification JWT que vous avez créé et cliquez sur Bind API. Dans la boîte de dialogue, sélectionnez les API du groupe d'API et de l'environnement spécifiés, ajoutez-les à la liste de droite, puis cliquez sur OK.
Si une API est déjà liée à un plug-in du même type, le nouveau plug-in remplace l'existant. Procédez avec prudence.
La fonctionnalité de débogage d'API dans la console API Gateway ne prend pas en charge le plug-in d'authentification JWT. Pour tester les API liées au plug-in, utilisez un outil tel que Postman ou exécutez la commande curl.
3. Exemple de code pour un service d'émission de tokens
import java.security.PrivateKey;
import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.NumericDate;
import org.jose4j.lang.JoseException;
public class GenerateJwtDemo {
public static void main(String[] args) throws JoseException {
// Use the keyId that is set in API Gateway.
String keyId = "uniq_key";
// Use the key pair that you generated in Section 2.1.
String privateKeyJson = "{\n"
+ " \"kty\": \"RSA\",\n"
+ " \"d\": "
+
"\"O9MJSOgcjjiVMNJ4jmBAh0mRHF_TlaVva70Imghtlgwxl8BLfcf1S8ueN1PD7xV6Cnq8YenSKsfiNOhC6yZ_fjW1syn5raWfj68eR7cjHWjLOvKjwVY33GBPNOvspNhVAFzeqfWneRTBbga53Agb6jjN0SUcZdJgnelzz5JNdOGaLzhacjH6YPJKpbuzCQYPkWtoZHDqWTzCSb4mJ3n0NRTsWy7Pm8LwG_Fd3pACl7JIY38IanPQDLoighFfo-Lriv5z3IdlhwbPnx0tk9sBwQBTRdZ8JkqqYkxUiB06phwr7mAnKEpQJ6HvhZBQ1cCnYZ_nIlrX9-I7qomrlE1UoQ\",\n"
+ " \"e\": \"AQAB\",\n"
+ " \"kid\": \"myJwtKey\",\n"
+ " \"alg\": \"RS256\",\n"
+ " \"n\": \"vCuB8MgwPZfziMSytEbBoOEwxsG7XI3MaVMoocziP4SjzU4IuWuE_DodbOHQwb_thUru57_Efe"
+
"--sfATHEa0Odv5ny3QbByqsvjyeHk6ZE4mSAV9BsHYa6GWAgEZtnDceeeDc0y76utXK2XHhC1Pysi2KG8KAzqDa099Yh7s31AyoueoMnrYTmWfEyDsQL_OAIiwgXakkS5U8QyXmWicCwXntDzkIMh8MjfPskesyli0XQD1AmCXVV3h2Opm1Amx0ggSOOiINUR5YRD6mKo49_cN-nrJWjtwSouqDdxHYP-4c7epuTcdS6kQHiQERBd1ejdpAxV4c0t0FHF7MOy9kw\"\n"
+ "}";
JwtClaims claims = new JwtClaims();
claims.setGeneratedJwtId();
claims.setIssuedAtToNow();
// The expiration time must be set and be less than 7 days.
NumericDate date = NumericDate.now();
date.addSeconds(120*60);
claims.setExpirationTime(date);
claims.setNotBeforeMinutesInThePast(1);
claims.setSubject("YOUR_SUBJECT");
claims.setAudience("YOUR_AUDIENCE");
// Add custom parameters. All values must be of the String type.
claims.setClaim("userId", "1213234");
claims.setClaim("email", "userEm***@youapp.com");
JsonWebSignature jws = new JsonWebSignature();
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
// This parameter is required.
jws.setKeyIdHeaderValue(keyId);
jws.setPayload(claims.toJson());
PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyJson)).getPrivateKey();
jws.setKey(privateKey);
String jwtResult = jws.getCompactSerialization();
System.out.println("Generate Json Web token , result is " + jwtResult);
}
}
Notez les points suivants :
La valeur
keyIddoit être globalement unique et correspondre aux trois emplacements suivants :
La valeur
keyIdque vous spécifiez lors de la génération d'une paire de clés dans la section 2.1 Générer une paire de clés JSON Web Key (JWK).La valeur
kidque vous configurez dans le plug-in d'authentification JWT dans la section 2.3 Configurer la clé publique dans le plug-in d'authentification JWT.La valeur
keyIddans le code, qui correspond à la valeur de la propriétéKeyIdHeaderValuede l'objetJsonWebSignature. Cette propriété est obligatoire.
-
Pour
privateKeyJson, utilisez la chaîne JSON JWK de la clé privée. Il peut s'agir de celle générée en ligne dans la section 2.1 ou de la chaîne JSONprivateKeyStringsi vous avez généré la paire de clés localement.privateKeyStringest une chaîne JSON. La période de validité est obligatoire et doit être inférieure à sept jours.
Utilisez des valeurs de type String pour tous les paramètres personnalisés.
4. Réponses d'erreur d'API Gateway
|
Status |
Code |
Message |
Description |
|
400 |
I400JR |
JWT required |
Paramètre JWT introuvable. |
|
403 |
S403JI |
Claim jti is required when preventJtiReplay:true |
La claim |
|
403 |
S403JU |
Claim jti in JWT is used |
La valeur |
|
403 |
A403JT |
Invalid JWT: ${Reason} |
Le JWT fourni dans la requête est invalide. |
|
400 |
I400JD |
JWT Deserialize Failed: ${Token} |
Échec de la désérialisation du JWT fourni dans la requête. |
|
403 |
A403JK |
No matching JWK, kid:${kid} not found |
La valeur |
|
403 |
A403JE |
JWT is expired at ${Date} |
Le JWT fourni dans la requête a expiré. |
|
400 |
I400JP |
Invalid JWT plugin config: ${JWT} |
Le plug-in d'authentification JWT est configuré incorrectement. |
Si vous recevez un code d'état inattendu, vérifiez l'en-tête de réponse X-Ca-Error-Code pour obtenir le ErrorCode et l'en-tête X-Ca-Error-Message pour obtenir le ErrorMessage. Si le code d'erreur est A403JT ou I400JD, utilisez jwt.io pour vérifier la validité et le format de votre token.