ブログ | NGINX

NGINX と NGINX Plus を使用した OAuth 2.0 アクセス トークンの検証

NGINX-F5 水平黒タイプ RGB の一部
リアム・クリリー サムネイル
リアム・クリリー
2019年5月13日公開

 

画像提供: unsplash.comのJohn T.

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 つあります。

  • RFC 7519で定義された JSON Web Token (JWT)
  • 認証されたクライアントの一意の識別子に過ぎない不透明なトークン

認証後、クライアントは保護されたリソースにアクセスするために、各 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 ベンダーとクラウド プロバイダーによってサポートされています。

どのトークン形式が使用されているかに関係なく、各バックエンド サービスまたはアプリケーションで検証を実行すると、多くの重複コードと不要な処理が発生します。 さまざまなエラー条件とエッジケースを考慮する必要があり、各バックエンド サービスでこれを行うと、実装に一貫性がなくなり、結果として予測できないユーザー エクスペリエンスが生じます。 各バックエンド サービスが次のエラー状態をどのように処理するかを検討します。

  • アクセストークンがありません
  • 非常に大きなアクセストークン
  • アクセス トークンに無効な文字または予期しない文字が含まれています
  • 複数のアクセストークンが提示されました
  • バックエンドサービス間のクロックスキュー
トークン検証を実行するバックエンド アプリケーション

NGINX auth_requestモジュールを使用してトークンを検証する

コードの重複とそれに伴う問題を回避するために、バックエンド サービスに代わって NGINX を使用してアクセス トークンを検証できます。 これにはいくつかの利点があります:

  • リクエストは、クライアントが有効なトークンを提示した場合にのみバックエンドサービスに到達します。
  • 既存のバックエンドサービスは、コードを変更することなくアクセストークンで保護できます。
  • IdPに登録する必要があるのはNGINXインスタンスのみ(すべてのアプリではない)
  • トークンの欠落や無効なトークンなど、あらゆるエラー条件で動作が一貫しています。
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 変数を使用します。

NGINX JavaScript モジュールによるauth_request の拡張

前述したように、 auth_requestモジュールをこのように使用することは完全な解決策ではありません。 auth_requestモジュールはHTTPステータスコードを使用して成功を判断します( 2xx = 良い、 4xx = 悪い)。 しかし、OAuth 2.0トークンイントロスペクションレスポンスは成功または失敗をJSONオブジェクトにエンコードし、HTTPステータスコードを返します。 200(OK)どちらの場合も。

有効なトークンのトークン イントロスペクション応答の JSON 形式

必要なのは、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.confload_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 は、イントロスペクション応答をキャッシュすることで、この欠点に対する最適化を提供できます。

最適化1: NGINXによるキャッシュ

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 秒ごとにトークン イントロスペクション要求を行う際のレイテンシ コストのみを負担するようになります。

最適化2: NGINX Plus による分散キャッシュ

コンテンツ キャッシュとトークン イントロスペクションを組み合わせると、セキュリティへの影響をほとんど与えずに、アプリケーション全体のパフォーマンスを向上させる非常に効果的な方法になります。 ただし、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 形式を使用するため、トークン イントロスペクション応答では引用符にエスケープが自動的に適用されることに注意してください。

最適化3: イントロスペクションレスポンスから属性を抽出する

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 コンテンツにリダイレクトされます。"