API 呼び出しを認証するためのオプションは、X.509 クライアント証明書から HTTP 基本認証まで多数あります。 しかし近年、OAuth 2.0アクセス トークンという形で事実上の標準が登場しました。 これらはクライアントから API サーバーに渡される認証資格情報であり、通常は HTTP ヘッダーとして送信されます。
しかし、OAuth 2.0 は相互接続された標準の迷路です。 OAuth 2.0 認証フローを発行、提示、検証するプロセスは、多くの場合、いくつかの関連する標準に依存します。 執筆時点では、 OAuth 2.0 標準は 8 つあり、アクセス トークンはその好例です。OAuth 2.0 コア仕様 ( RFC 6749 ) では、アクセス トークンの形式は指定されていません。 現実世界では、一般的に使用されている形式は 2 つあります。
認証後、クライアントは保護されたリソースにアクセスするために、各 HTTP リクエストでアクセス トークンを提示します。 アクセス トークンが信頼できる ID プロバイダー (IdP) によって発行され、有効期限が切れていないことを確認するには、アクセス トークンの検証が必要です。 IdP は発行する JWT に暗号署名するため、IdP に実行時に依存せずに JWT を「オフライン」で検証できます。 通常、JWT には有効期限も含まれており、これも確認できます。 NGINX Plus auth_jwt
モジュールはオフラインの JWT 検証を実行します。
一方、不透明トークンは、それを発行した IdP に送り返して検証する必要があります。 ただし、これには、以前にログインしたセッションをアクティブのままにせずに、たとえばグローバル ログアウト操作の一環として、このようなトークンを IdP によって取り消すことができるという利点があります。 グローバル ログアウトでは、IdP で JWT を検証する必要が生じる場合もあります。
このブログでは、NGINX と NGINX Plus が OAuth 2.0 のRelying Partyとして機能し、検証のためにアクセス トークンを IdP に送信し、検証プロセスに合格したリクエストのみをプロキシする方法について説明します。 このタスクに NGINX と NGINX Plus を使用することのさまざまな利点と、検証応答を短時間キャッシュすることでユーザー エクスペリエンスを向上させる方法について説明します。 NGINX Plus については、 NGINX Plus R18で導入された JavaScript モジュールを使用してキー値ストアを更新することで、NGINX Plus インスタンスのクラスター全体にキャッシュを分散する方法も示します。
特に記載がない限り、このブログの情報は NGINX Open Source と NGINX Plus の両方に適用されます。 NGINX Plus への参照はその製品にのみ適用されます。
IdP を使用してアクセス トークンを検証する標準的な方法は、トークン イントロスペクションと呼ばれます。 RFC 7662 (OAuth 2.0 トークン イントロスペクション) は現在、広くサポートされている標準であり、依存当事者が IdP にトークンを提示するために使用する JSON/REST インターフェイスと、応答の構造について説明しています。 多くの主要な IdP ベンダーとクラウド プロバイダーによってサポートされています。
どのトークン形式が使用されているかに関係なく、各バックエンド サービスまたはアプリケーションで検証を実行すると、多くの重複コードと不要な処理が発生します。 さまざまなエラー条件とエッジケースを考慮する必要があり、各バックエンド サービスでこれを行うと、実装に一貫性がなくなり、結果として予測できないユーザー エクスペリエンスが生じます。 各バックエンド サービスが次のエラー状態をどのように処理するかを検討します。
auth_request
モジュールを使用してトークンを検証するコードの重複とそれに伴う問題を回避するために、バックエンド サービスに代わって NGINX を使用してアクセス トークンを検証できます。 これにはいくつかの利点があります:
NGINX が 1 つ以上のアプリケーションのリバース プロキシとして機能する場合、バックエンドにリクエストをプロキシする前に、 auth_request
モジュールを使用して IdP への API 呼び出しをトリガーできます。 すぐにわかるように、次のソリューションには根本的な欠陥がありますが、 auth_request
モジュールの基本的な操作が導入されており、これについては後のセクションで詳しく説明します。
auth_request
ディレクティブ (5 行目) は、API 呼び出しを処理する場所を指定します。 バックエンドへのプロキシ (6 行目) は、 auth_request
応答が成功した場合にのみ実行されます。 auth_request の
場所は 9 行目に定義されています。 外部クライアントが直接アクセスできないようにするため、内部
としてマークされています。
11 行目から 14 行目では、トークン イントロスペクション要求形式に準拠するように要求のさまざまな属性を定義します。 イントロスペクション要求で送信されるアクセス トークンは、14 行目に定義されている本文のコンポーネントであることに注意してください。 ここで、 token=$http_apikey
は、クライアントがapikey
リクエスト ヘッダーでアクセス トークンを提供する必要があることを示します。 もちろん、アクセス トークンはリクエストの任意の属性で提供できます。その場合は、別の NGINX 変数を使用します。
auth_request の
拡張前述したように、 auth_request
モジュールをこのように使用することは完全な解決策ではありません。 auth_request
モジュールはHTTPステータスコードを使用して成功を判断します( 2xx
= 良い、 4xx
= 悪い)。 しかし、OAuth 2.0トークンイントロスペクションレスポンスは成功または失敗をJSONオブジェクトにエンコードし、HTTPステータスコードを返します。 200
(OK)
どちらの場合も。
必要なのは、IdP のイントロスペクション応答を適切な HTTP ステータス コードに変換して、 auth_request
モジュールがその応答を正しく解釈できるようにする JSON パーサーです。
ありがたいことに、JSON 解析は NGINX JavaScript モジュール (njs) にとって簡単なタスクです。 したがって、トークン イントロスペクション要求を実行するためにlocation
ブロックを定義する代わりに、 auth_request
モジュールに JavaScript 関数を呼び出すように指示します。
[編集者– この投稿は、NGINX JavaScript モジュールの使用例を探るいくつかの投稿のうちの 1 つです。 完全なリストについては、 「NGINX JavaScript モジュールの使用例」を参照してください。
このセクションのコードは、 js_インポート
指令は、 js_include
指令の NGINX プラス R23 そしてその後。 詳細については、 NGINX JavaScript モジュールのリファレンス ドキュメントを参照してください。構成例のセクションに、NGINX 構成と JavaScript ファイルの正しい構文が示されています。 ]
注記: このソリューションでは、 nginx.confのload_module
ディレクティブを使用して JavaScript モジュールを動的モジュールとしてロードする必要があります。 手順については、 NGINX Plus 管理者ガイドを参照してください。
13 行目のjs_content
ディレクティブは、JavaScript 関数introspectAccessToken を
auth_request
ハンドラーとして指定します。 ハンドラー関数はoauth2.jsで定義されています。
introspectAccessToken
関数が、以下の構成スニペットで定義されている別の場所 ( /oauth2_send_request ) に HTTP サブリクエスト (行 2) を送信することに注意してください。 次に、JavaScript コードは応答を解析し (5 行目)、アクティブ
フィールドの値に基づいて適切なステータス コードをauth_request
モジュールに返します。 有効な(アクティブな)トークンが返される ウェブ 204
(いいえ
コンテンツ)
(ただし成功)無効なトークンが返される ウェブ 403
(禁断)
。 エラー状態が返される HTTP 401
(無許可)
エラーと無効なトークンを区別できるようにするためです。
注記: このコードは概念実証としてのみ提供されており、製品品質ではありません。 包括的なエラー処理とログ記録を備えた完全なソリューションを以下に示します。
2 行目に定義されているサブリクエストのターゲットの場所は、元のauth_request
構成と非常によく似ています。
トークン イントロスペクション リクエストを構築するためのすべての構成は、 /_oauth2_send_request の場所内に含まれています。 通常、必要な構成項目は、認証 (19 行目)、アクセス トークン自体 (21 行目)、およびトークン イントロスペクション エンドポイントの URL (22 行目) のみです。 IdP がこの NGINX インスタンスからのトークン イントロスペクション要求を受け入れるには、認証が必要です。 OAuth 2.0 トークン イントロスペクション仕様では認証が義務付けられていますが、方法は指定されていません。 この例では、 Authorization
ヘッダーでベアラー トークンを使用します。
この構成が完了すると、NGINX はリクエストを受信すると、それを JavaScript モジュールに渡し、IdP に対してトークン イントロスペクション リクエストを実行します。 IdP からの応答が検査され、アクティブ
フィールドがtrue の
場合、認証は成功したとみなされます。 このソリューションは、NGINX を使用して OAuth 2.0 トークン イントロスペクションを実行するコンパクトで効率的な方法であり、他の認証 API にも簡単に適応できます。
しかし、まだ終わりではありません。 一般的にトークン イントロスペクションの最大の課題は、すべての HTTP リクエストに遅延が追加されることです。 問題の IdP がホスト型ソリューションまたはクラウド プロバイダーである場合、これは重大な問題になる可能性があります。 NGINX と NGINX Plus は、イントロスペクション応答をキャッシュすることで、この欠点に対する最適化を提供できます。
OAuth 2.0トークンイントロスペクションはJSON/RESTエンドポイントでIdPによって提供されるため、標準のレスポンスはHTTPステータスを含むJSONボディになります。200
。 この応答がアクセス トークンに対してキー化されると、キャッシュ可能性が高くなります。
NGINX は、各アクセス トークンのイントロスペクション応答のコピーをキャッシュするように構成できます。これにより、次に同じアクセス トークンが提示されたときに、NGINX は IdP への API 呼び出しを行う代わりに、キャッシュされたイントロスペクション応答を提供します。 これにより、後続のリクエストの全体的なレイテンシが大幅に改善されます。 期限切れまたは最近取り消されたアクセス トークンを受け入れるリスクを軽減するために、キャッシュされた応答が使用される期間を制御できます。 たとえば、API クライアントが通常、短期間に複数の API 呼び出しを集中的に行う場合、ユーザー エクスペリエンスを測定可能な程度に向上させるには、10 秒のキャッシュ有効期間で十分な可能性があります。
キャッシュは、ストレージ(キャッシュ(イントロスペクション応答)用のディスク上のディレクトリとキー(アクセス トークン)用の共有メモリ ゾーン)を指定することによって有効になります。
proxy_cache_path
ディレクティブは、必要なストレージを割り当てます。イントロスペクション応答用の/var/cache/nginx/oauthと、キー用のtoken_responsesというメモリ ゾーンです。 これはhttp
コンテキストで構成されているため、 server ブロック
とlocation
ブロックの外側に表示されます。 キャッシュ自体は、トークン イントロスペクション応答が処理されるlocation
ブロック内で有効になります。
proxy_cache
ディレクティブ (行 26) により、この場所のキャッシュが有効になります。 デフォルトでは、NGINX は URI に基づいてキャッシュしますが、この場合は、 apikey
リクエスト ヘッダー (行 27) で提示されたアクセス トークンに基づいて応答をキャッシュします。
28 行目では、 proxy_cache_lock
ディレクティブを使用して、同じキャッシュ キーを持つ同時リクエストが到着した場合、最初のリクエストがキャッシュにデータを入力するまで待機してから他のリクエストに応答する必要があることを NGINX に伝えます。 proxy_cache_valid
ディレクティブ (行 29) は、NGINX にイントロスペクション応答をキャッシュする期間を指示します。 このディレクティブがない場合、NGINX は IdP から送信されたキャッシュ制御ヘッダーからキャッシュ時間を決定します。ただし、これらは常に信頼できるとは限らないため、応答のキャッシュ方法に影響を与える可能性のあるヘッダーを無視するように NGINX に指示します (行 30)。
キャッシュが有効になったことで、アクセス トークンを提示するクライアントは、10 秒ごとにトークン イントロスペクション要求を行う際のレイテンシ コストのみを負担するようになります。
コンテンツ キャッシュとトークン イントロスペクションを組み合わせると、セキュリティへの影響をほとんど与えずに、アプリケーション全体のパフォーマンスを向上させる非常に効果的な方法になります。 ただし、NGINX が分散形式で展開されている場合(複数のデータセンター、クラウド プラットフォーム、アクティブ/アクティブ クラスターなど)、キャッシュされたトークン イントロスペクション応答は、イントロスペクション要求を実行した NGINX インスタンスでのみ使用できます。
NGINX Plus では、メモリ内のキー値ストアであるkeyval
モジュールを使用して、トークン イントロスペクション応答をキャッシュできます。 さらに、 zone_sync
モジュールを使用して、NGINX Plus インスタンスのクラスター全体でこれらの応答を同期することもできます。 つまり、どの NGINX Plus インスタンスがトークン イントロスペクション要求を実行したかに関係なく、応答はクラスター内のすべての NGINX Plus インスタンスで利用できます。
注記: 実行時状態共有のためのzone_sync
モジュールの構成は、このブログの範囲外です。 NGINX Plus クラスターでの状態共有の詳細については、 NGINX Plus 管理者ガイドを参照してください。
NGINX Plus R18以降では、 keyval
ディレクティブで宣言された変数を変更することで、キー値ストアを更新できます。 JavaScript モジュールはすべての NGINX 変数にアクセスできるため、応答の処理中にイントロスペクション応答をキー値ストアに入力できます。
NGINX ファイルシステム キャッシュと同様に、キー値ストアはストレージを指定することで有効化されます。この場合は、キー (アクセス トークン) と値 (イントロスペクション応答) を格納するメモリ ゾーンです。
keyval_zone
ディレクティブのtimeout
パラメータを使用して、 auth_request_cache.confの 29 行目と同じ 10 秒のキャッシュされた応答の有効期間を指定していることに注意してください。これにより、NGINX Plus クラスターの各メンバーは、期限が切れると応答を個別に削除します。 2 行目は、各エントリのキーと値のペアを指定します。キーはapikey
リクエスト ヘッダーで提供されるアクセス トークンであり、値は$token_data
変数によって評価されるイントロスペクション応答です。
これで、 apikey
リクエスト ヘッダーを含む各リクエストに対して、以前のトークン イントロスペクション応答があれば、 $token_data
変数にその応答が入力されます。 したがって、JavaScript コードを更新して、トークン イントロスペクション応答がすでにあるかどうかを確認します。
2 行目は、このアクセス トークンのキー値ストア エントリがすでに存在するかどうかをテストします。 イントロスペクション応答を取得するには 2 つのパス (キー値ストアから、またはイントロスペクション応答から) があるため、検証ロジックを次の別の関数tokenResult
に移動します。
これで、各トークン イントロスペクション応答はキー値ストアに保存され、NGINX Plus クラスターの他のすべてのメンバー間で同期されます。 次の例は、有効なアクセス トークンを使用した単純な HTTP リクエストと、それに続く NGINX Plus API へのクエリを示しており、キー値ストアの内容を表示します。
$ curl -IH "apikey: tQ7AfuEFvI1yI-XNPNhjT38vg_reGkpDFA" http://localhost/ HTTP/1.1 200 OK 日付: 2019年4月24日水曜日 17:41:34 GMT コンテンツタイプ: application/json コンテンツ長: 612 $ curl http://localhost/api/4/http/keyvals/access_tokens {"tQ7AfuEFvI1yI-XNPNhjT38vg_reGkpDFA":"{\"active\":true}"}
キー値ストア自体は JSON 形式を使用するため、トークン イントロスペクション応答では引用符にエスケープが自動的に適用されることに注意してください。
OAuth 2.0 トークン イントロスペクションの便利な機能は、レスポンスにトークンのアクティブ ステータスに加えてトークンに関する情報を含めることができることです。 このような情報には、トークンの有効期限や、関連付けられているユーザーの属性(ユーザー名、電子メール アドレスなど)が含まれます。
この追加情報は非常に役立ちます。 ログに記録したり、きめ細かいアクセス制御ポリシーを実装するために使用したり、バックエンド アプリケーションに提供したりできます。 これらの属性は、成功した(HTTP)リクエストで追加のレスポンスヘッダーとして送信することで、 auth_request
モジュールにエクスポートできます。204
) 応答。
イントロスペクション応答の各属性を反復処理し (行 23)、それを応答ヘッダーとしてauth_request
モジュールに送り返します。 標準の応答ヘッダーとの競合を避けるために、各ヘッダー名にはToken-
というプレフィックスが付けられます (行 26)。 これらの応答ヘッダーは、NGINX 変数に変換され、通常の構成の一部として使用できるようになりました。
この例では、ユーザー名
属性を新しい変数$username
(行 11) に変換します。 auth_request_set
ディレクティブを使用すると、トークン イントロスペクション応答のコンテキストを現在のリクエストのコンテキストにエクスポートできます。 各属性の応答ヘッダー(JavaScript コードによって追加される)は、 $sent_http_token_ attribute
として使用できます。 12 行目には、バックエンドにプロキシされるリクエスト ヘッダーとして$username
の値が含まれます。 トークン イントロスペクション応答で返される任意の属性に対してこの構成を繰り返すことができます。
上記のコードと構成の例は機能的であり、概念実証テストや特定のユースケースに合わせたカスタマイズに適しています。 実稼働環境での使用には、追加のエラー処理、ログ記録、柔軟な構成を強くお勧めします。 NGINX と NGINX Plus のより堅牢で詳細な実装は、GitHub リポジトリでご覧いただけます。
このブログでは、NGINX auth_request
モジュールを JavaScript モジュールと組み合わせて使用し、クライアント要求に対して OAuth 2.0 トークン イントロスペクションを実行する方法を説明しました。 さらに、キャッシュを使用してソリューションを拡張し、NGINX 構成で使用するためにイントロスペクション応答から属性を抽出しました。
また、NGINX Plus インスタンスのクラスター全体にわたる本番環境の展開に適した、イントロスペクション応答の分散キャッシュとして NGINX Plus キーバリュー ストアを使用する方法についても説明しました。
NGINX Plus を使用した OAuth 2.0 トークン イントロスペクションをぜひお試しください。今すぐ30 日間の無料トライアルを開始するか、弊社にお問い合わせの上、ユースケースについてご相談ください。
「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"