Alibaba Cloud API Gateway (API Gateway) は、JSON Web トークン (JWT) を使用して、ご利用のユーザーシステムに基づいて API アクセスを権限付与します。これにより、カスタムのセキュリティ対策を実装できます。
1. トークンベースの認証
概要
多くのパブリック API は、リクエストされたリソースへのアクセスを許可するかどうかを判断するために、呼び出し元を識別する必要があります。トークンは、ID 検証に使用されるメカニズムです。トークンを使用することで、アプリケーションはサーバーにユーザー認証やセッション情報を保存する必要がなくなります。これにより、ステートレスで分散された Web アプリケーションの権限付与が可能になり、アプリケーションのスケーリングが簡素化されます。
ワークフロー
上の図は、API Gateway JWT プラグインの認証ワークフローを示しています。ワークフローは以下のとおりです。
クライアントは、トークンを含むリクエストを API Gateway に送信します。
API Gateway は、JWT プラグインで構成された公開鍵を使用して、リクエスト内のトークンを検証します。トークンが有効な場合、API Gateway はリクエストをバックエンドサービスに渡します。
バックエンドサービスはリクエストを処理し、応答を返します。
API Gateway は、バックエンドサービスからの応答をクライアントに返します。
このプロセス中、API Gateway はトークン認証メカニズムを使用して、ご利用のユーザーシステムに基づいて API アクセスを権限付与します。以下のセクションでは、API Gateway が認証に使用する構造化トークンである JWT について説明します。
JWT
1.1 概要
JWT は、Web アプリケーション環境で当事者間でクレームを安全に送信するための JSON ベースのオープン標準 (RFC 7519) です。JWT は、自己完結型の認証トークンとして機能します。ユーザー ID、ユーザーロール、クライアントがリソースサーバーからリソースにアクセスすることを許可する権限などの情報を含めることができます。JWT には、ビジネスロジックで必要とされる追加のクレームを含めることもできます。JWT は、分散サイトでのログインシナリオに最適です。
1.2 JWT の構成
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ上記の例に示すように、JWT は次の 3 つの部分で構成される文字列です。
ヘッダー
ペイロード
署名
ヘッダー
ヘッダーには、次の 2 つの情報が含まれています。
トークンのタイプ。JWT です。
使用する暗号化アルゴリズムを指定します。
完全なヘッダーは、次の例に示すような JSON オブジェクトです。
{
'typ': 'JWT',
'alg': 'HS256'
}その後、ヘッダーは Base64url エンコードされ、JWT の最初の部分を形成します。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9ペイロード
ペイロードには、有用な情報が含まれています。詳細は以下のとおりです。
iss: トークンの発行者。このクレームは、誰がトークンを作成したかを示す文字列です。
sub: サブジェクト識別子。発行者によって提供されるエンドユーザーの識別子です。発行者のスコープ内で一意であり、大文字と小文字を区別し、最大長は 255 ASCII 文字です。
aud: 受信者。トークンの対象となる受信者です。これは、大文字と小文字を区別する文字列の配列です。
exp: 有効期限。トークンが有効期限切れになるタイムスタンプです。この時刻を過ぎると、トークンは無効になります。このクレームは、1970-01-01T00:00:00Z からの秒数を表す整数です。
iat: 発行時刻。トークンが発行された時刻です。このクレームは、1970-01-01T00:00:00Z からの秒数を表す整数です。
jti: JWT ID。トークンの一意の識別子です。このクレームの値は、発行者によって作成される各トークンで一意です。競合を防ぐために、通常は暗号学的にランダムな値が使用されます。この値は、攻撃者が取得できないランダムエントロピーコンポーネントを構造化トークンに追加し、トークン推測やリプレイ攻撃を防ぐのに役立ちます。ご利用のユーザーシステムで必要とされるカスタムクレームを追加することもできます。たとえば、次のコードに示すように、ユーザーのニックネームの name クレームを追加できます。
{
"sub": "1234567890",
"name": "John Doe"
}その後、ペイロードは Base64url エンコードされ、JWT の 2 番目の部分を形成します。
JTdCJTBBJTIwJTIwJTIyc3ViJTIyJTNBJTIwJTIyMTIzNDU2Nzg5MCUyMiUyQyUwQSUyMCUyMCUyMm5hbWUlMjIlM0ElMjAlMjJKb2huJTIwRG9lJTIyJTBBJTdE署名
署名を作成するには、Base64 エンコードされたヘッダーと Base64 エンコードされたペイロードをピリオド (.) で結合します。次に、ヘッダーで指定されたアルゴリズムを使用して、$secret で表される秘密鍵で結果の文字列に署名します。この署名が JWT の 3 番目の部分を形成します。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, '$secret');これら 3 つの部分をピリオド (.) で連結して、このセクションの冒頭にある JWT の例 に示すように、完全な JWT 文字列を形成します。
1.3 権限付与の範囲と有効期間
API Gateway は、同じ API グループ内で JWT プラグインにバインドされているすべての API へのアクセス権をトークンに付与します。よりきめ細かい権限管理を行うには、ご利用のバックエンドサービスでトークンを解析し、独自の権限チェックを実行する必要があります。API Gateway は、トークン内の `exp` クレームを検証します。トークンが期限切れの場合、API Gateway はそれを無効とみなし、リクエストを拒否します。有効期限を設定する必要があり、有効期間は 7 日未満でなければなりません。
1.4 JWT の特徴
デフォルトでは、JWT は暗号化されません。JWT に機密データを含めないでください。
JWT は、認証と情報交換に使用できます。JWT を効果的に使用すると、サーバーでのデータベースクエリの数を減らすことができます。JWT の主な欠点は、そのステートレスな性質です。これは、トークンが有効期限切れになる前に、そのトークンを取り消したり、権限を変更したりできないことを意味します。JWT が一度発行されると、サーバーが失効を処理するための追加ロジックをデプロイしない限り、有効期限が切れるまで有効です。
JWT には認証情報が含まれています。漏洩した場合、誰でもそのトークンに関連付けられているすべての権限を取得できてしまいます。このリスクを軽減するために、JWT の有効期間を短く設定してください。高度なセキュリティを必要とする操作については、アクセスを許可する前にユーザーを再認証してください。
盗難のリスクを軽減するために、HTTP 経由で JWT をプレーンテキストで送信しないでください。代わりに HTTPS を使用してください。
2. JWT プラグインを使用した API の保護
2.1 JWK キーペアの生成
2.1.1 オンラインでの生成
https://tools.top/jwt-encode.html にアクセスして、トークンの生成と検証のための秘密鍵と公開鍵を生成できます。秘密鍵は、権限付与サービスが JSON Web Key (JWK) を発行するために使用されます。公開鍵は、API Gateway がリクエスト署名を検証するために JWT プラグインで構成されます。API Gateway は、RSA SHA256 アルゴリズムと 2048 ビットのキーサイズのキーペアをサポートしています。
2.1.2 ローカルでの生成
このセクションでは、Java での例を示します。他のプログラミング言語でも同様のツールを見つけてキーペアを生成できます。まず、Maven プロジェクトを作成し、次の依存関係を追加します。
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.7.0</version>
</dependency>次のコードを使用して、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 JWK の秘密鍵を使用したトークン発行サービスの実装
オンラインで生成された秘密鍵 JWK JSON 文字列、または セクション 2.1 でローカルに生成された privateKeyString JSON 文字列を秘密鍵として使用して、トークンを発行します。これらのトークンは、信頼できるユーザーが保護された API にアクセスすることを権限付与します。詳細については、「トークン発行サービスのサンプルコード」をご参照ください。
トークンを発行するメソッドは、ビジネスシナリオによって異なります。トークン発行機能を本番環境にデプロイし、標準 API として構成できます。ユーザーは、ユーザー名とパスワードを提供することでトークンを取得できます。または、ローカル環境でトークンを生成し、特定のユーザーに直接提供することもできます。
2.3 JWT プラグインでの JWK 公開鍵の構成
API Gateway コンソール にログインします。
左側のナビゲーションウィンドウで、を選択します。
プラグイン管理ページで、右上の [プラグインの作成] をクリックします。
[プラグインの作成] ページで、[プラグイン名] と [プラグインタイプ] を設定します。次のコードは、JWT 認証プラグインの構成例を示しています。構成の詳細については、「JWT 認証プラグイン」をご参照ください。
---
parameter: X-Token # JWT を取得するパラメーター。API のパラメーターに対応します。
parameterLocation: header # JWT を読み取る場所。API リクエストモードがマッピングの場合、このパラメーターはオプションです。API リクエストモードがパススルーの場合、このパラメーターは必須です。有効な値:query、header。
claimParameters: # クレームパラメーター変換。ゲートウェイは JWT クレームをバックエンドパラメーターにマッピングします。
- claimName: aud # クレーム名。パブリッククレームとプライベートクレームがサポートされています。
parameterName: X-Aud # マッピングされるパラメーター名。
location: header # マッピングされるパラメーターの場所。有効な値:query、header、path、formData。
- claimName: userId # クレーム名。パブリッククレームとプライベートクレームがサポートされています。
parameterName: userId # マッピングされるパラメーター名。
location: query # マッピングされるパラメーターの場所。有効な値:query、header、path、formData。
preventJtiReplay: false # jti のリプレイ防止チェックを有効にするかどうかを指定します。デフォルト値:false。
#
# JSON Web Key (JWK) の公開鍵。セクション 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-sj4cCRWq3bd61Zs2QOJ8iustH0vSRMYdP5oYQ2.4 API への JWT プラグインのバインド
プラグイン管理ページで、作成した JWT 認証プラグインを見つけ、[API のバインド] をクリックします。表示されるダイアログボックスで、指定したグループと環境から API を選択し、右側のリストに追加してから、[OK] をクリックします。

API Gateway コンソールの API デバッグ機能は、JWT プラグインをサポートしていません。JWT プラグインにバインドされている API をテストするには、Postman などのツールを使用するか、curl コマンドを実行します。
3. トークン発行サービスのサンプルコード
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 {
// API Gateway で設定された keyId を使用します。
String keyId = "uniq_key";
// セクション 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();
// 有効期限は 7 日未満に設定する必要があります。
NumericDate date = NumericDate.now();
date.addSeconds(120*60);
claims.setExpirationTime(date);
claims.setNotBeforeMinutesInThePast(1);
claims.setSubject("YOUR_SUBJECT");
claims.setAudience("YOUR_AUDIENCE");
// カスタムパラメーターを追加します。すべての値は String 型である必要があります。
claims.setClaim("userId", "1213234");
claims.setClaim("email", "userEm***@youapp.com");
JsonWebSignature jws = new JsonWebSignature();
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
// このパラメーターは必須です。
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);
}
}上記の例に関する注意点は以下のとおりです。
keyIdはグローバルに一意であり、以下の場所で一貫している必要があります。
JSON Web Key (JWK) キーペアの生成セクションでキーを生成する際に指定する
keyId。JWT プラグインでの JWK 公開鍵の構成セクションで構成する
kid。コード内の
keyId。これはJsonWebSignatureオブジェクトのKeyIdHeaderValueプロパティの値です。このプロパティは必須です。
privateKeyJson パラメーターは、セクション 2.1 で説明されているようにオンラインで生成されるか、ローカルで生成される公開鍵の JWK JSON 文字列です。
privateKeyStringJSON 文字列。有効期間は必須であり、7 日未満にする必要があります。
すべてのカスタムパラメーターには String 値を使用してください。
4. API Gateway のエラー応答
ステータス | コード | メッセージ | 説明 |
400 | I400JR | JWT が必要です | JWT パラメーターが見つかりません。 |
403 | S403JI | preventJtiReplay:true の場合、クレーム jti が必要です | JWT 認証プラグインでリプレイ防止機能が有効になっている場合、リクエストは有効な jti を提供していません。 |
403 | S403JU | JWT のクレーム jti が使用されています | JWT 認証プラグインでリプレイ防止機能が有効になっている場合、リクエストで提供された jti は既に使用されています。 |
403 | A403JT | 無効な JWT:${Reason} | リクエストで提供された JWT は無効です。 |
400 | I400JD | JWT のデシリアライズに失敗しました:${Token} | リクエストで提供された JWT の解析に失敗しました。 |
403 | A403JK | 一致する JWK がありません。kid:${kid} が見つかりません | リクエスト JWT の kid がどの JWK とも一致しません。 |
403 | A403JE | JWT の有効期限が ${Date} に切れました | リクエストで提供された JWT は期限切れです。 |
400 | I400JP | 無効な JWT プラグイン構成:${JWT} | JWT 認証プラグインの構成が正しくありません。 |
予期しないステータスコードを受け取った場合は、HTTP 応答の X-Ca-Error-Code ヘッダーで ErrorCode を、X-Ca-Error-Message ヘッダーで ErrorMessage を確認してください。エラーコードが A403JT または I400JD の場合は、jwt.io Web サイトを使用して、ご利用の token の有効性とフォーマットを確認できます。