レルムは隔離の境界だ
一言でいうと
レルムは、ユーザー、クライアント、ロール、鍵を収める完全な分離の単位です。レルムが違えば、お互いのユーザーを知りません。
なぜ必要なのか
Keycloakを最初に開くと、masterレルムがあります。ここにアプリケーションのユーザーを作るのが、最初のミスです。masterはKeycloak自体を管理するレルムで、ここのユーザーはKeycloakの管理権限と結びつきます。アプリケーション用のレルムは、別に作らなければなりません。
レルムが分離の単位だという言葉の意味は、具体的です。レルムごとに署名鍵が違い、ユーザーの保存先が違い、トークンの発行者(iss)が違います。そのため、レルムAのトークンは、レルムBのAPIでは検証に失敗します。マルチテナントのSaaSで、テナントごとにレルムを置く設計は、ここから出てきます。ただし、レルムの数が数百個になると管理とメモリの負担が大きくなるため、たいていは1つのレルムの中で、グループと属性でテナントを区別します。
どう動くのか
クライアントは、レルムの中でトークンをリクエストできるアプリケーションです。2種類に分かれます。
パブリッククライアントは、シークレットを持ちません。SPAとモバイルアプリがここに当たり、PKCEを必ず有効にしなければなりません。リダイレクトURIを正確に登録することも重要です。ワイルドカードを広く使うと、トークンを盗まれる入り口になります。
機密クライアントは、シークレットを持ちます。バックエンドサーバーがここに当たり、サービスアカウントを有効にすると、client credentialsフローで自分自身のトークンを受け取れます。
ロールは、2つの層があります。レルムロールはレルム全体で意味を持ち(例: admin)、クライアントロールは特定のクライアントの中でだけ意味を持ちます(例: orders-apiのrefund)。サービスが複数あるなら、クライアントロールで分けたほうが、名前の衝突を防げます。
グループは、ユーザーの集まりで、ロールをマッピングできます。ユーザー300人に個別にロールを与える代わりに、グループにロールを与えて、ユーザーをグループに入れます。グループは階層を持てて、下位のグループは上位のロールを継承します。
コンポジットロールは、ロールが他のロールを含むものです。order-adminがorder-readerを含むようにすれば、管理者に2つのロールをそれぞれ与える必要がありません。
現場での姿
トークンにロールが入る位置は、混乱しやすいところです。レルムロールはrealm_access.rolesに、クライアントロールはresource_access.<클라이언트ID>.rolesに入ります(プレースホルダーはクライアントIDです)。そして、クライアントの「full scope allowed」をオフにすると、そのクライアントにマッピングされていないロールはトークンから外れます。トークンのサイズを減らし、最小権限を守るために使われますが、これを知らずにオフにすると、「ロールを与えたのにトークンに見えません」ということになります。
管理作業は、kcadm.shでスクリプト化するのがよいでしょう。Webコンソールで手作業で作った設定は再現できず、ステージングと本番が静かに食い違います。
トークンを検証する側で見るべきこと
発行の設定をどれだけしっかり行っても、受け取る側がきちんと検証しなければ、何の意味もありません。APIサーバーがトークンを受け取ったときに確認すべきことは、決まっています。
- 署名: レルムの公開鍵で検証します。鍵は、
/.well-known/openid-configurationが指すJWKSのアドレスから取得してキャッシュし、知らない鍵IDが来たら取得し直します。鍵はローテーションされるので、一度取得して永久に使い続けると、ローテーションの日にすべて失敗します。 - 発行者(
iss): 自分たちのレルムが発行したものかどうか。これを見ないと、他のレルムや別のKeycloakが発行したトークンも通ってしまいます。 - 対象(
aud): このトークンが自分たちに向けられたものかどうか。なければ、他のサービス向けに発行されたトークンが、自分たちのAPIにそのまま使われます。 - 有効期限(
exp): 時計のずれは、数十秒程度だけ許容します。余裕を大きく取るほど、盗まれたトークンの寿命が長くなります。
ここで絶対にしてはいけないのが、アルゴリズムをトークンの指示どおりに選ぶことです。検証する側がヘッダーのalgをそのまま信じると、攻撃者がnoneや共通鍵のアルゴリズムに書き換えて、署名を回避できます。期待するアルゴリズムをコードに固定しておき、それと違えばただちに拒否しなければなりません。
トークンの寿命の設計も、併せて決めます。アクセストークンは短く(数分)、リフレッシュトークンは長くする理由は、アクセストークンは取り消せないからです。署名が有効で期限前なら、Keycloakでユーザーを削除しても、そのトークンは使い続けられます。そのため、即座に遮断する必要がある場合は、短い寿命に頼るか、リクエストごとにトークンの状態を確認する方式に変える必要がありますが、後者はすべてのリクエストがKeycloakを経由するので、性能と可用性を合わせて考慮する必要があります。「ログアウトしたのに、なぜまだ使えるのですか」という質問の答えは、たいていこの部分にあります。
最後に、検証に失敗したときにどう返すかも決めておく必要があります。署名や発行者が間違っているなら401で、トークンは有効なのにそのロールではできない操作なら403です。この2つをひとまとめにすると、クライアントが再ログインすべき状況と、権限を受け取るべき状況を区別できず、無限に再ログインを試みる画面ができあがります。
次のラボですること
レルムを作り、パブリッククライアントと機密クライアントをそれぞれ作り、ユーザーを作ります。その後、別のラボでロールとグループを作り、トークンにどのように反映されるかを確認し、スコープでロールを除外するところまで行います。