こんにちは。TIGのLEEです。認証認可連載の3本目です。
前回はAWS API Gatewayを利用して、OIDC/OAuth2.0におけるResource ServerをCustom Authorizerで保護する記事を書いてました。
https://future-architect.github.io/articles/20210610a/
今回はAPI Gatewayのミドルウェア製品となるKongを使ってResource Serverを構築する方法について話します。
Kong Gateway
KongはOSSから始まったAPIサーバのトラフィックを管理するためのミドルウェアです。
nginxベースにLuaJITエンジンを使ってLuaスクリプトが組み込めるWebプラットフォームのOpenRestyを採用し、Luaで書かれた様々なPlug-inをデフォルトで揃え、それを組み立てることでAPIGatewayの機能を実装しています。また、Luaスクリプトで新しいカスタムPlug-inを作りそれを組み込むことも可能です。
Enterprise版が登場してからはAPIGateway以外にも様々さサービスがありますが、今回はKong Gatewayにのみ注目して行きたいです。
Kongの構造
構築の話になる前にかんたんにKongの構造を触れていきます。上の図のようにKongは基本的にConsumer/Route/Service/LoadBalancer(Upstream)の4つのレイヤリングが存在します。Kongで使う様々なPlug-inはこの4つのレイヤーのどこか、もしくはGlobalに組み込むこともできます。
- Consumer: Kongを実際利用するAPIClient(もしくはユーザ)を表すエンティティ
- Route: Requestのルールを定義するエンティティ
- Service: KongがProxyするBackendServiceを表すエンティティ
- Upstream: Backendの負荷分散やHealthCheckなどに使う仮想ホストのエンティティ
Actors
構築にあたり、まずはOIDCの役者を揃えましょう。Front/Backで分離された認証認可設計のためには、少なくとも下記3つのActorが必要になります。
Keycloak as OpenID Provider (Authorization Server)
https://www.keycloak.org/getting-started/getting-started-docker
中心となる認可サーバはOSSのKeycloakを使いましょう。ID管理、トークンや証明書の発行&管理、認証画面提供などの役割があります。
今回は上記リンク通り、Dockerを利用して構築します。Client設定は下記のVueの設定に従いましょう。
Keycloak構築はチュートリアル通りで問題ないので、詳細な実装方法は省略します。
Vue as Relying Party (Client)
https://www.keycloak.org/securing-apps/vue
FrontendとなるClientはVueを使います。認証後トークンの保持&リフレッシュ、APIサーバへリクエストを送ったりします。
今回はVueを使いますが、keycloak-jsさえ組み込めば、どのFrameworkでもかんたんにRelyingPartyを作ることができます。
Keycloak同様リンク通り実装すれば問題ないので、詳細は省略します。
Kong as Resource Server (API Server, Backend Service)
今回の保護対象となるResourceServerは、Gatewayとして前段に位置するKongと本丸となるBackend Service (API Server)に構成されます。Backend ServiceとしてはKongのチュートリアルで使われるMockbinをそのまま使います。
Kongの構築
https://docs.konghq.com/gateway/latest/install/
まずはKongをインストールします。Dockerなど便利なオプションもあるので好きな方法でインストールしましょう。
Kongは設定の保存先としてDBを使うのでPostgreSQLもインストールが必要です。
Kongはデフォルトで
- Port 8001:あらゆるエンティティ設定を行うのAdminAPI
- Port 8000:実際トラフィックをさばくProxy
に分かれています。
Service & Routing
https://docs.konghq.com/gateway/latest/get-started/services-and-routes/
次にKongとBackendServiceとなるMockbinをつないで、KongのURLにアクセスするとMockbinのレスポンスが出るようにします。
curl -i -s -X POST http://localhost:8001/services \ |
上記のように設定することでKongの/mock
へのアクセスが、Mockbinの/
にProxyされるようになります。
curl -X GET http://localhost:8000/mock/requests |
そうすると上のようなCurlでMockbinのAPIパスである/requests
のレスポンスが取得できます。
APIを保護する
ここまで下準備が整ったところで、本題である認証機能実装に入ります。
ClientからのリクエストはBearerTokenとしてKeycloakが発行したJWTを乗せないと拒否するようにしたいので、トークンを検証するためにKong公式のJWTプラグインを使います。
curl -X POST http://localhost:8001/plugins -d "name=jwt" |
今回はJWTプラグインをGlobalに設定しますが、特定のServiceやRouteに限定して設定することも可能です。
Consumer
次はConsumerの設定です。ConsumerはAPI Clientを表すエンティティですが、今回の場合は特定認可サーバ(Keycloak)に認証済みのユーザ全員を表すために予め設定するものになります。
curl -X POST http://localhost:8001/consumers -d "username=authorized_user" |
JWT Credential
最後にConsumerにJWTを検証するための公開鍵を設定することで、「この検証されたトークンのBearerはこのConsumerで間違いない」ということを認証させるための設定をします。
curl -X POST http://localhost:8001/consumers/authorized_user/jwt \ |
key
JWTのペイロードiss
と同じ値を設定します。
このAPIにアクセスできるユーザ(authorized_user
)は、みんな同じ認可サーバ(Issuer
)から発行されたトークンを持ってる(Bearer
)ことを意味します。
JWTプラグインのデフォルト設定でconfig.key_claim_name=iss
となるので、別のClaimの値にしたい場合(例えばaud
かazp
など)はAdminAPIの/plugins/{jwt plug-in ID}
をPATCHなどして変更も可能です。
algorithm
Keycloakでデフォルトで発行するAccessToken(JWT)のアルゴリズムであるRS256
を指定します。
注意するところは、もしこの設定のリクエストで下記のrsa_public_key
のPEM形式が正しくない場合でも、このフィールドのエラーメッセージが出ます。
rsa_public_key
Keycloakは同じRealmのユーザには同じ公開鍵でJWTを署名しているので、AdminConsoleのRealm Settings > Keys
からRS256
の公開鍵をPEM形式でセットします。
一般的にRS256のJWT検証に使われるJWKs Endpointの証明書(x5c
)と違い、公開鍵であることに注意しましょう。
実際リクエストを送ってみる
普通アプリを作るならばここでClientであるVue上でKeycloakから取得したAccessToken(JWT)をAuthorization
ヘッダーに載せ、KongのAPIにアクセスするコードを書くことになります。
しかし、ここではKongの機能を確認するだけでいいので、Vueが保持するKeycloakのインスタンスをダンプさせAccessTokenを取得し、curlを使います。
curl http://localhost:8000/mock/requests -H "Authorization: Bearer eyJhbGciOiJS..." | jq . |
そうするとMockbinが受け取ったHeaderを上記のようなレスポンスとして返してくれます。
最後に
といった感じで簡単に触ってみましたが、いかがだったでしょうか。
今回は割愛しましたが、exp
Claimで有効期限をチェックすることも可能ですし、設定のconfig.key_claim_name
とプラグインを適用するRoute/Serviceを調整する機能を組み合わせることで認可機能を実装することも可能です。
個人的にはどのアカウントからのリクエストかわかるように、ペイロードのsub
など一部のClaimを後ろにヘッダーとして流せる機能があったら良かったなとも思いましたが、例えばこういったカスタムプラグインを組み合わせることでなんとかなりそうです。