1. 質問とコンテキスト
複数のAPIリソースサーバーが認可サーバーによって発行されたアクセストークンを受け入れます。各サーバーは、トークンがアクティブであるか、どのクライアントが所有しているか、どのスコープを持っているか、いつ失効するかを知る必要があります。またセキュリティ上、盗難されたトークンの迅速な失効も求められます。設計では、リアルタイムの失効、低レイテンシー、および認可サービスの可用性のバランスを取る必要があります。
2. 面接官が評価しているポイント
- RFC 7662のイントロスペクションおよびRFC 7009の失効セマンティクスを理解しているか。
- キャッシュ、失効イベント、およびバージョンによって、古いアクティブ判定結果が失効を隠蔽してしまうのを防げているか。
- 不透明トークン(opaque token)、ローカルJWT検証、およびDPoPなどの所持証明(proof-of-possession)を区別できているか。
- 障害時ポリシー、リトライ、テナント分離、および監査ポリシーが明確であるか。
3. 回答前の明確化のための質問
- トークンは短命なアクセストークン、リフレッシュトークン、またはその両方ですか?
- 単一のトークン、グラント(認可)、ユーザーセッション、またはクライアント全体のうち、どれを失効させますか?
- リソースサーバーは失効伝播の遅延をどの程度許容できますか?
- コピーされたベアラートークンのリプレイを防ぐために、クライアントキーまたはDPoP公開キーをバインドする必要がありますか?
4. 30秒の回答フレームワーク
エンドポイント、信頼できる情報源(source of truth)、キャッシュ、失効伝播、および障害ポリシーを用いて回答を構成します。
認可サーバーは保護されたイントロスペクションおよび失効エンドポイントを公開します。ステートストアはトークンハッシュ、クライアント、スコープ、有効期限、および失効バージョンを保持します。リソースサーバーはアクティブな結果を短いTTLでキャッシュし、失効イベントをサブスクライブして即座にエントリを削除します。高リスクなスコープは認可障害時にフェイルクローズとし、低リスクなリクエストは期限内のポジティブキャッシュを一時的に利用できます。クエリ、失効、および障害はすべて監査ログに記録されます。
5. ステップごとの詳細解説
ステップ 1: トークン状態とエンドポイント認可の定義
イントロスペクションを呼び出せるのは信頼できるリソースサーバーのみに制限します。不要なIDデータを露出させることなく、active、client_id、username、scope、exp、iat、subなどの必要なフィールドを返します。失効エンドポイントは呼び出し元とトークンタイプを認証し、重複失効についてはRFC 7009に準拠し、既に無効なトークンが存在していたかどうかを開示しません。
ステップ 2: ステートストレージとインデックスの設計
トークンのフィンガープリントまたはハッシュをキーとして使用します。発行時刻、有効期限、グラントID、クライアント、スコープ、状態バージョン、および失効理由を保存します。一括失効のためにグラントIDにインデックスを付与し、機密プレーンテキストはログから除外します。単一クライアントがホットパーティションにならないよう、テナントまたはハッシュによってシャーディングします。
ステップ 3: キャッシュと失効伝播の処理
ポジティブな結果は短いTTLでキャッシュし、長いネガティブキャッシュは避けます。失効後はトークンのフィンガープリントと状態バージョンを発行します。リソースサーバーはローカルエントリを即座に削除し、定期的なポーリングまたはバージョンチェックによって未受信イベントを修復します。リアルタイム失効がレイテンシーに見合う高リスクな操作に対しては、オンラインイントロスペクションを強制します。
ステップ 4: 障害、リトライ、および所持証明の処理
イントロスペクションのタイムアウト時には、リソースサーバーが認可サービスに過負荷をかけないよう、指数バックオフとサーキットブレーカーを使用します。サービス停止時は、スコープのリスクに応じてフェイルクローズまたは期限内のポジティブキャッシュの短期利用を選択します。DPoPを使用する場合は、トークンにバインドされた公開キーに対してリクエスト証明も検証し、コピーされたベアラートークンのリプレイリスクを軽減します。
6. 高品質な回答例
システムを認可エンドポイント、イントロスペクション、失効、ステートストア、失効イベントバス、およびリソースサーバーSDKに分割します。ステートストアはトークンハッシュをキーとし、クライアント、スコープ、発行・失効時刻、グラントID、失効状態、およびバージョンを保持します。イントロスペクションはリソースサーバーが必要とするフィールドのみを返し、呼び出し元の認証を必須とします。
>
SDKは通常スコープを30秒間キャッシュし、高リスクなスコープはオンラインでイントロスペクションします。失効は単一トークンおよびグラント全体の失効をサポートし、その後バージョン付きイベントを発行します。SDKは受信時にキャッシュを削除し、定期的な修復ジョブが未受信イベントを処理します。バージョンが一致しなくなったポジティブキャッシュエントリは、使用前に再イントロスペクションする必要があります。
>
認可タイムアウト時、低リスクなリクエストは期限内のポジティブキャッシュを利用できますが、高リスクなリクエストは拒否されます。サーキットブレーカーとジッター付きリトライによってカスケード障害を防ぎます。DPoPを使用する場合、SDKはリクエスト証明とバインドされたキーを検証します。クエリ、失効、キャッシュヒット、および障害はマスク処理された監査ストレージに送信され、盗難トークンの影響範囲を追跡できるようにします。
7. よくあるアンチパターン
- ローカルJWT署名検証をリアルタイム失効と同等として扱う。
- 失効イベントやバージョンチェックなしに、アクティブ結果を長期間キャッシュする。
- イントロスペクションをパブリックに公開したり、不要なユーザーフィールドを返したりする。
- 認可サービスの停止時にすべてのリクエストをフェイルオープンにする。
- タイムアウト、サーキットブレーカー、またはジッターなしでリトライを行い、認証スタンピードを引き起こす。
8. フォローアップの質問と回答
フォローアップ 1: なぜネガティブキャッシュは通常短く設定すべきなのですか?
トークンが発行されたばかりである可能性、レプリカが古延している可能性、あるいは失効伝播中である可能性があるためです。ネガティブキャッシュを短く保つことで、一時的なデータ不達が長い無効期間に変わってしまうのを防ぎます。
フォローアップ 2: リフレッシュトークンを失効させた場合、アクセストークンも失効させるべきですか?
グラントおよびセキュリティポリシーに基づきます。リフレッシュトークンが盗難された場合は、通常そのグラントに紐づく残りのトークンも失効させ、バージョンまたはイベントを介してリソースサーバーのキャッシュを無効化させます。
フォローアップ 3: 重複または順序が狂った失効イベントをどのように防ぎますか?
単調増加するバージョンまたは失効時刻を含めます。コンシューマーはローカル状態と同等以上の新しいバージョンのみを受け入れます。これにより重複イベントは冪等に処理され、古いイベントがアクティブ状態を復活させることはなくなります。