Authentication between services | API Gateway Documentationの翻訳
エンドユーザーのリクエストを認証することに加えて、API にリクエストを行うサービス(人間以外のユーザー)を認証したい場合があります。このページでは、サービスアカウントを使用して、人間またはサービスに認証を提供する方法について説明します。
API にリクエストを送信するサービスを識別するには、サービス アカウントを使用します。呼び出し側のサービスは、サービス アカウントの秘密鍵を使用して安全な JSON Web Token(JWT)に署名し、署名した JWT をリクエストに含めて API に送信します。
API と呼び出し側のサービスにサービス間認証を実装するには、次の手順に従います。
- 呼び出し側のサービスのサービス アカウントとキーを作成します。
- API Gateway サービスの OpenAPI ドキュメントに認証サポートを追加します。
- 次の処理を行うコードを呼び出し側のサービスに追加します。
- JWT を作成し、サービス アカウントの秘密鍵で署名する。
- リクエストで署名済みの JWT を API に送信する。
API Gateway は、リクエストを API に転送する前に、JWT のクレームが API Config の構成と一致することを検証します。API Gateway は、サービス アカウントに付与されている Cloud Identity の権限を確認しません。
Cloud Identity - サービスアカウントの IAM 権限のこと
すでに以下を行っていることを前提としています。
- Google Cloud プロジェクトが作成されていること
- API の OpenAPI ドキュメントが作成済みであること
- API Config が作成済みであること
「呼び出し元のサービスが JWT の署名に使用する秘密鍵ファイル」を持つサービスアカウントが必要です。API にリクエストを送信するサービスが複数ある場合は、1 つのサービスアカウントを作成してすべての呼び出しサービスを表すことができます。サービス間で区別する必要がある場合 (例えば、サービスの権限が異なる場合など)、呼び出し元のサービスごとにサービスアカウントとキーを作成することができます。
このセクションでは、Google Cloud Console と gcloud
コマンドライン ツールを使用してサービス アカウントと秘密鍵ファイルを作成し、サービス アカウントに Service Account Token Creator ロールを割り当てる方法を説明します。API を使用してこのタスクを実行する方法については、「サービス アカウントの作成と管理」を参照してください。
サービス アカウントとキーを作成するには、次の手順に従います。
- Cloud Console で、[サービス アカウント キーの作成] ページに移動します。
- [サービス アカウント キーの作成] ページに移動
- 使用するプロジェクトを選択します。
- [サービス アカウント] プルダウン メニューから [新しいサービス アカウント] を選択します。
- [サービス アカウント名] フィールドに名前を入力します。
- [役割] プルダウン メニューで、[サービス アカウント]、[サービス アカウント トークン作成者] の順に選択します。
- キータイプは、デフォルト タイプ JSON を使用します。
- [作成] をクリックします。サービス アカウントの秘密鍵を含む JSON ファイルがパソコンにダウンロードされます。
秘密鍵を保護する方法については、認証情報を管理する際のベスト プラクティスをご覧ください。
ゲートウェイ用の API Config を作成するとき、ゲートウェイが他のサービスと対話するために使用するサービスアカウントを指定します。ゲートウェイを呼び出すサービスに対してサービスアカウント認証を有効にするには、API コンフィグのセキュリティ要件オブジェクトとセキュリティ定義オブジェクトを変更します。以下の手順を実行すると、API ゲートウェイが、呼び出しサービスが使用する署名済み JWT のクレームを検証できるようになります。
- API Config でサービスアカウントを発行者(issuer)として追加します。
securityDefinitions:
DEFINITION_NAME:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "{SA_EMAIL_ADDRESS}"
x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/{SA_EMAIL_ADDRESS}"
DEFINITION_NAME
は、このセキュリティ定義を識別する文字列に置き換えます。サービス アカウント名または呼び出し側のサービスを識別する名前に置き換えることをおすすめします。SA_EMAIL_ADDRESS
は、サービス アカウントのメールアドレスに置き換えます。- API Config で複数のセキュリティ定義を定義することができますが、それぞれの定義は異なる x-google-issuer を持つ必要があります。呼び出し元のサービスごとに別々のサービスアカウントを作成している場合は、例えば、サービスアカウントごとにセキュリティ定義を作成することができます。
securityDefinitions:
service-1:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "[email protected]"
x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]"
service-2:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "[email protected]"
x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]"
-
必要に応じて、
x-google-audiences
をsecurityDefinitions
セクションに追加します。x-google-audiences
を追加しない場合、API Gateway は JWT の"aud"
(audience)クレームがhttps://SERVICE_NAME
の形式であることを要求します。SERVICE_NAME
は API Gateway サービスの名前で、OpenAPI ドキュメントのhost
フィールドで設定したものです。 -
API 全体に適用する場合はファイルのトップ レベル (インデントやネストされていない場所) に、特定のメソッドに適用する場合はメソッド レベルに
security
セクションを追加します。API レベルとメソッド レベルの両方でsecurity
セクションを使用する場合は、メソッド レベルの設定が API レベルの設定を上書きします。
security:
- DEFINITION_NAME: []
DEFINITION_NAME
を、securityDefinitions
セクションで使用した名前に置き換えます。securityDefinitions
セクションに複数の定義がある場合は、次のようにsecurity
セクションに追加します。
security:
- service-1: []
- service-2: []
- 新しい API Config をデプロイします。
API Gateway がリクエストを API に転送する前に、APIGateway は以下を検証します
- API Config の
x-google-jwks_uri
フィールドで指定した URI にある公開鍵を利用した JWT の署名 - JWT の
"iss"
(issuer) のクレームがx-google-issuer
フィールドで指定された値と一致していること - JWT の
"aud"
(audience) クレームに API Gateway サービス名が含まれているか、x-google-audiences
フィールドで指定した値と一致している "exp
(expiration time)クレームを使った、トークンの有効期限の確認
x-google-issuer
、x-google-jwks_uri
、x-google-audiences
の詳細については、OpenAPI の拡張を参照してください
認証されたリクエストを行う場合、呼び出し側のサービスは、OpenAPI ドキュメントに指定されたサービス アカウントで署名された JWT を送信します。呼び出し側のサービスは、次の処理を行う必要があります。
- JWT を作成し、サービス アカウントの秘密鍵で署名します。
- リクエストで署名済みの JWT を API に送信します。
次のサンプルコードは、このプロセスを一部の言語で示しています。他の言語で認証済みリクエストを行うには、jwt.io で、サポートされているライブラリの一覧を参照してください。
- 呼び出し側のサービスに以下の関数を追加し、次のパラメータを渡します。
saKeyfile
: サービス アカウントの秘密鍵ファイルへのフルパス。saEmail
: サービス アカウントのメールアドレス。audience
:x-google-audiences
フィールドを API config に追加している場合、x-google-audiences
で指定したいずれかの値をaudience
に設定します。それ以外の場合は、audience
にhttps://SERVICE_NAME
を設定します。ここで、SERVICE_NAME
は、API Gateway のサービス名です。expiryLength
: JWT の有効期限(秒)。
この関数は JWT を作成して秘密鍵ファイルで署名し、署名済みの JWT を返します。
// generateJWT creates a signed JSON Web Token using a Google API Service Account.
func generateJWT(saKeyfile, saEmail, audience string, expiryLength int64) (string, error) {
now := time.Now().Unix()
// Build the JWT payload.
jwt := &jws.ClaimSet{
Iat: now,
// expires after 'expiryLength' seconds.
Exp: now + expiryLength,
// Iss must match 'issuer' in the security configuration in your
// swagger spec (e.g. service account email). It can be any string.
Iss: saEmail,
// Aud must be either your Endpoints service name, or match the value
// specified as the 'x-google-audience' in the OpenAPI document.
Aud: audience,
// Sub and Email should match the service account's email address.
Sub: saEmail,
PrivateClaims: map[string]interface{}{"email": saEmail},
}
jwsHeader := &jws.Header{
Algorithm: "RS256",
Typ: "JWT",
}
// Extract the RSA private key from the service account keyfile.
sa, err := ioutil.ReadFile(saKeyfile)
if err != nil {
return "", fmt.Errorf("Could not read service account file: %v", err)
}
conf, err := google.JWTConfigFromJSON(sa)
if err != nil {
return "", fmt.Errorf("Could not parse service account JSON: %v", err)
}
block, _ := pem.Decode(conf.PrivateKey)
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("private key parse error: %v", err)
}
rsaKey, ok := parsedKey.(*rsa.PrivateKey)
// Sign the JWT with the service account's private key.
if !ok {
return "", errors.New("private key failed rsa.PrivateKey type assertion")
}
return jws.Encode(jwsHeader, jwt, rsaKey)
}
- 呼び出し側のサービスに、以下の関数を追加し、署名付き JWT を
Authorization: Bearer
ヘッダー内で API に送信します。
// makeJWTRequest sends an authorized request to your deployed endpoint.
func makeJWTRequest(signedJWT, url string) (string, error) {
client := &http.Client{
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", fmt.Errorf("failed to create HTTP request: %v", err)
}
req.Header.Add("Authorization", "Bearer "+signedJWT)
req.Header.Add("content-type", "application/json")
response, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("HTTP request failed: %v", err)
}
defer response.Body.Close()
responseData, err := ioutil.ReadAll(response.Body)
if err != nil {
return "", fmt.Errorf("failed to parse HTTP response: %v", err)
}
return string(responseData), nil
}
JWT を使用してリクエストを送信する場合は、セキュリティ上の理由から、認証トークンを Authorization: Bearer
ヘッダーに入れることをおすすめします。例:
curl --request POST \
--header "Authorization: Bearer ${TOKEN}" \
"${GATEWAY_URL}/echo"
GATEWAY_URL
と TOKEN
はそれぞれ、Gateway の URL と認証トークンを格納する環境変数です。
通常、API Gateway は受信したすべてのヘッダーを転送します。ただし、バックエンド アドレスが OpenAPI 仕様の x-google-backend
で指定されている場合は、元の Authorization
ヘッダーより優先します。
API Gateway は認証結果を X-Apigateway-Api-Userinfo
でバックエンド API に送信します。元の Authorization
ヘッダーではなく、このヘッダーを使用することをおすすめします。このヘッダーは base64url
エンコードされ、JWT payload が含まれています。