トップ ブログ Keycloakを使ってAPI GatewayでAPIをアクセス制限する

Keycloakを使ってAPI GatewayでAPIをアクセス制限する

2025/03/05

本記事は、NRIエンジニアによって2023年12月21日にQiitaに投稿された記事です。

本記事は、「Keycloakを使ってAPI GatewayでAPIをアクセス制限する」の記事を元に、Amazon API Gatewayを使ったアクセス制限(認可)として、クライアントアプリ向けのAPIサーバーをAmazon API GatewayとAWS lamdaで構築して、どのようにアクセス制限が行えるのか検証する。

本記事でやること

  • Keycloakサーバ設定
  • Lambda関数作成
  • API Gateway作成
  • 動作検証

Amazon API Gatewayとは

Amazon API Gatewayは、様々な規模の REST、HTTPなどのAPIを作成、公開、維持、モニタリング、セキュア化できる AWS のフルマネージドサービスのことである。
世間一般的なAPI Gatewayとは何か、についてはこちらの記事で説明されているので本記事では割愛する。

今回の構築する構成

以下のような環境を構築する。
認証についてはKeycloakを利用して、
APIのアクセス制御に関してはAPI GatewayのLambda オーソライザー機能を利用してトークン(JWT)の検証処理を行う。

Keycloakサーバ設定

レルムを作成

Realm nameをapi-gatewayとしてレルムを作成する。

ユーザー作成

作成したレルムに所属するユーザーとして、今回は検証用に以下の2つのユーザーを作成する。

ユーザー名 用途
testuser1

ロール未所属のユーザー

testuser2

api-gateway-roleのロールに所属するユーザー

ロール作成&ユーザー追加

ロール名をapi-gateway-roleとしてロールを作成し、作成済みのtestuser2ユーザーを作成したロールに所属させる。

Keycloakの公開鍵情報取得

左のメニューからレルムの設定タブを押下して、公開鍵情報を確認する。RS256公開ボタンを押下することで、公開鍵情報を表示される。

公開鍵はLambdaオーソライザーのトークン検証処理の実装時に利用するため、後で確認できるようにメモしておく。

Lambda関数作成

AWSのLambdaサービスの画面を開き、関数の作成ボタンから以下の2つのLambda関数を作成する。ランタイムなどは何でもよいが、今回はNode.jsで作成した。

API業務処理関数作成

業務用の関数はほぼデフォルトのままとし、以下の通り返却するJson文字列だけ変更した。

lamda業務関数

export const handler = async(event) => {
  const response = {
      statusCode: 200,
      body: JSON.stringify('Get resource!!!'),
  };
  return response;
};

オーソライザー用関数作成

オーソライザー関数についてはこちらをベースに、トークン検証用にjsonwebtokenを利用する形で実装した。

Lambdaオーソライザー関数

import * as jsonwebtoken from 'jsonwebtoken';
export const handler =  function(event, context, callback) {
    // トークンを取得
    var token = event.authorizationToken.replace("Bearer ", "");

    // Keycloak側の公開鍵
    const publicKey = "-----BEGIN PUBLIC KEY-----\r\n"
                      + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..."
                      + "\r\n-----END PUBLIC KEY-----";

    // トークンの検証
    jsonwebtoken.verify(token, publicKey, (err, decoded) => {
        if (err) {
            // 検証NG (403 Error)
            console.log(err);
            callback(null, generatePolicy('user', 'Deny', event.methodArn));
        } else {
            // 検証OK
            var roles = decoded.realm_access.roles
            if(roles.includes('api_gateway_test_role')) {
                callback(null, generatePolicy('user', 'Allow', event.methodArn));
            } else {
                // 検証NG (403 Error)
                console.log('権限なし');
                callback(null, generatePolicy('user', 'Deny', event.methodArn));
            }
        }
    });
};

// ポリシーを作成
var generatePolicy = (principalId, effect, resource) => {
    var authResponse = {};

    authResponse.principalId = principalId;
    if (effect && resource) {
        var policyDocument = {};
        policyDocument.Version = '2012-10-17';
        policyDocument.Statement = [];
        var statementOne = {};
        statementOne.Action = 'execute-api:Invoke';
        statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement[0] = statementOne;
        authResponse.policyDocument = policyDocument;
    }

    return authResponse;
}

オーソライザー関数では以下のような処理を行っている。

  1. リクエストヘッダからBearerを除去したトークンを取得
  2. Keycloakの公開鍵を用いてトークンを検証
  3. 検証成功時、デコードしたトークンからroleの情報を取り出してapi_gateway_test_roleのロールを持っているか確認
    ※トークン検証NGの時などは、エラーとして403応答が返却
  4. ロール保持時、Invokeの権限を付与したポリシーを作成してコールバック
    ※ロール非保持の場合、権限をDenyに設定してコールバック

本記事では公開鍵を直接Lambda関数に埋め込んだが、JWKsエンドをKeycloakで設定することで都度参照するようにすることも可能

API Gateway作成

API作成

REST APIを選択してAPIを作成する。API名以外はデフォルトの設定値のままとする。

リソース&メソッド作成

API一覧から作成したAPIを選択して、アクションボタン → リソース作成ボタンを押下してリソースの作成画面を開く。

リソース名を入力して、リソース作成ボタンを押下すると、以下のように作成したリソースがAPIに追加される。

次にメソッドを作成する。
作成したリソースを選択した状態で、アクションボタン → メソッドの作成ボタンを押下すると、リソースの下にメソッドのセレクトボックスが表示される。GETを選択してリストの右にある✓ボタンを押下するとメソッドの設定画面が開かれるので作成したLambda関数等を設定していく。

Lamda関数の欄に事前に作成済みの関数名を入力して保存ボタンを押下する。
これでAPIのtest_resourceパスのGETメソッドに作成したLambda関数が統合された。

APIデプロイ

作成したAPIを外部からアクセスできるようにデプロイを行う。
アクションボタン → メソッドの作成ボタンを押下してデプロイするステージを選択する。ステージが未作成の場合は、新規作成選択およびステージ名を入力してからデプロイボタンを押下する。

デプロイすると以下のようにデプロイしたリソースがステージに含まれる。

テスト実行

ここで作成したAPIを実行して動作確認してみる。現時点ではアクセス制限をしていない状態のため、API Gatewayは単純なプロキシーをする動きのみとなっている。
APIを実行するクライアントとしてPostmanを利用して、以下のようにtest_resourceパスのGETでリクエストを送信する。

リクエストにはアクセストークンなど一切設定していないがひとまずAPIの正常疎通が確認できた。

オーソライザー作成

最後にAPI Gatewayにオーソライザーを作成する。
API Gatewayの左側のメニューからオーソライザーを選択して、新しいオーソライザーの作成を押下してオーソライザーを作成する。

設定項目 設定内容
名前

任意でOK

Lambda関数

事前に作成したオーソライザー用Lambda関数

Lambda呼び出しロール

未設定でOK

トークンのソース

トークンとして取得するHTTPヘッダの名称

トークン検証

トークンのバリデーション

認可のキャッシュ

任意で設定

トークンソースのソースは実際のHTTPヘッダの名称を指定するが、Lambda関数内で取得するときはauthorizationTokenとなる。

続けて作成したオーソライザーをAPIに設定する。
リソースからオーソライザーを設定するメソッドを選択して。メソッドリクエストの部分を押下する。

メソッドリクエストの設定画面が開くので、認可の部分で作成したオーソライザーを設定する。

ここまで完了したらオーソライザーを適用したAPIを再度デプロイして準備完了。

動作検証

今回作成したAPIはapi_gateway_test_roleを持っているユーザーのみアクセスを許可するようにオーソライザーを設定しているので、対象のロールを持っていないユーザー、持っているユーザーのトークンを用いてAPIを実行し、期待通りに制限できているか検証する。

まず、対象のロールを持っていないtestuser1で取得したトークンでAPIを実行する。
トークン取得についてはKeycloak標準であるaccount_connsoleなどでログインして取得し、PostmanのAuthorizationヘッダに付与して実行する。
その結果、以下のように必要なロールを保持していないため認可されず、403応答でレスポンスが返却される。

Lambdaのログを確認するとしっかり検証失敗時に埋め込んだログが出力されているため、オーソライザーとして作成したLambda関数で検証されていることが確認できる。

次に、対象のロールを持っているtestuser2で取得したトークンでAPIを実行する。
先ほど同様に取得したトークンをPostmanのAuthorizationヘッダに付与して実行すると、以下のように必要なロールを保持しているため認可され、200応答でレスポンスが返却される。

以上でAmazon API GatewayとKeycloakを使ってAPIのアクセス制限を確認することができた。

最後に

今回は簡易的な検証として、トークン(JWT)にある属性値の一致をみるだけのアクセス制限(認可)でした。実際にOIDCとしての認可ではトークン・インストロスペクション・エンドポイントへトークンを送り、現在アクティブかどうかや権限情報を取得するような動きとすべきかと思います。そのあたりについては機会があればまた検証してみたいと思います。

お気軽にお問い合わせください
オープンソースに関するさまざまな課題、OpenStandiaがまるごと解決します。
下記コンテンツも
あわせてご確認ください。