JWTとNGINX Plusによる認証とコンテンツベースルーティング

NGINX | 2018年3月01日

NGINX Plus Release 10では、JSON Web Token(JWT、「ジョット」と発音)を使用して、WebおよびAPIサービスから認証処理をオフロードする機能が導入されました。R10のリリース以降、新しいリリースのたびに機能を拡充してきました。

Starting in NGINX Plus R14以降、NGINX Plusは、ネストされたクレームや配列データを含むJWTをサポートしています。APIゲートウェイとして使用する場合、NGINX PlusはJWTを使用して、バックエンドサービスやAPIエンドポイントへの接続を要求するクライアントを認証できます。

NGINX PlusでJWTを認証し、そのJWT情報に基づいて、より高度な負荷分散を判断するための基本設定について、時折お問い合わせをいただきます。最も簡単な方法は、認証に成功した場合はサービスへのアクセスを許可し、失敗した場合は接続をブロックまたはリダイレクトすることです。

この記事では、NGINX Plusを使用したJWT認証とコンテンツベースルーティングについて、最初から最後までを網羅した概念実証(PoC)の手順を紹介します。できる限り幅広いユースケースに対応し、JWTに関する事前知識や経験がなくても理解できるよう、「JWT入門」として構成しました。例や背景情報も交えながら、JWTの知識がない方でもこのソリューションを導入できるよう解説します。

JWTの使用経験がある場合、または環境内に既存のJWTがある場合は、最初の2つのセクションを省略し、提供されているNGINX Plusの設定スニペットを環境に合わせて調整することで、JWTクレームデータに基づく高度な負荷分散の判断を行えるようになります。

前提条件

このドキュメントでは、NGINX Plusが新規インストールされ、デフォルトの設定ファイルが次の場所にあることを前提としています。

  • /etc/nginx/nginx.conf
  • /etc/nginx/conf.d/default.conf

NGINX Plusのインストール方法と使用開始方法の詳細については、「NGINX Plus Admin Guide」を参照してください。

すべてのCLIコマンドはroot権限での実行を前提としているため、root以外のユーザーには、使用する環境でsudo権限が必要です。

追加の背景情報や、NGINX PlusでJWTを使用するその他のユースケースについては、以下のリンクをご覧ください。

JWTと関連する署名鍵の作成

以下の手順では、NGINX PlusでJWTクレームの基本的な処理を設定する方法の例として、この例に固有のペイロードデータを使用してJWTを一から作成します。サンプルの代わりに既存のJWTを使用する場合は、「secrets」ファイルに、JWTの作成に使用した署名キーと一致するBase64URLエンコード文字列が含まれていることを確認する必要があります。また、JWTペイロード内のクレームも変更しなければならない可能性があります。

注: JWTの生成方法にかかわらず、Base64URLエンコーディングを使用する必要があります。Base64URLエンコーディングでは、Base64エンコーディングで一般的に使用されるパディング(=文字を使用)や、HTTPに準拠していないその他の文字が適切に処理されます。多くのツールではこれらが自動的に処理されますが、手動のCLIベースのBase64エンコーダーや一部のJWT作成ツールでは処理されません。Base64URLエンコーディングの詳細については、Brock Allenによる「base64url encoding」および「RFC 4648」を参照してください。

この例では、対称鍵方式のHS256 JWTを作成するために、jwt.ioのGUI(Base64URLエンコードが正しく行われます)を使用します。次のスクリーンショットは、以下の手順で指定された値を入力し、署名が検証された後のGUIを示しています。

Working in the GUI at jwt.ioのGUIで、右側の[Decoded]列にある各フィールドの指定値を確認または入力し、HS256 JWTを生成します。

  1. 次のデフォルト値がHEADERフィールドに表示されていることを確認し、必要に応じて内容を一致するように変更します。
{  "alg": "HS256",
  "typ": "JWT"
}
  1. In the VERIFY SIGNATUREフィールドで、ボックス内のデフォルト値「secret」を「nginx123」に置き換えます。(この2つの手順を逆にすると問題が発生するため、PAYLOADフィールドにデータを入力する前に、この変更を行ってください。)
  2. Replace the contents of the PAYLOADフィールドの内容を次のように置き換えます。
{  "exp": 1545091200,
  "name": "Create New User",
  "sub": "cuser",
  "gname": "wheel",
  "guid": "10",
  "fullName": "John Doe",
  "uname": "jdoe",
  "uid": "222",
  "sudo": true,
  "dept": "IT",
  "url": "http://secure.example.com"
}
  1. 注: expクレームは、JWTの有効期限の日時をUNIXエポック時間(1970年1月1日午前0時(UTC)からの経過秒数)で設定します。サンプル値は、2018年12月18日午前0時(UTC)を表します。有効期限を調整するには、エポック時間を変更してください。
  2. フィールドの下にあるバーが青色で、「Signature Verified」と表示されていることを確認します。
  3. 左側の[Encoded]列の値をファイルまたはバッファにコピーします。これは、ユーザーjdoeがhttp://secure.example.comにアクセスするために提示する必要があるJWTの全文です。後述のテストでもこの値を使用します(以下を参照)。ここでは表示上の都合によりJWTを改行していますが、NGINX Plusには1行の文字列として提示する必要があります。eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOi
    JDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkI
    joiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIy
    Iiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGx
    lLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4

NGINX Plusホスト上で、次の手順に従って、nginx123で署名されたJWTをNGINX Plusが検証する際に使用するキーファイルを作成します:

  1. 次のコマンドを実行して、署名文字列に対応するBase64URLエンコード済み文字列を生成します。(trコマンドは、Base64URLエンコードに必要な文字置換を行います。)
# echo -n nginx123 | base64 | tr '+/' '-_' | tr -d '='bmdpbngxMjM
  1. In the /etc/nginx/ディレクトリに、NGINX PlusがJWT署名の検証に使用する鍵ファイル「api_secret.jwk」を作成します。以下の内容を挿入してください。kフィールドの値は、前の手順で生成した「nginx123」をBase64URLでエンコードしたものです。
{"keys":    [{
        "k":"bmdpbngxMjM",
        "kty":"oct"
    }]
}

JWTを処理するためのNGINX Plusの設定

このセクションの手順では、リクエストに含まれるJWTを検証し、クライアントが認可されている場合に、認可されていないクライアントに表示されるデフォルトページではなく、保護されたリソースを提示するようにNGINX Plusを設定します。また、JWT関連情報を記録する新しいログ形式も定義します。

JWT検証とコンテンツベースルーティングの設定

この手順では、標準的なベストプラクティスに従い、NGINX Plusが読み込まないように構成ファイル「default.conf」の名前を変更し、テスト専用の新しい構成を作成します。これにより、テストの完了後やテスト中に問題が発生した場合に、デフォルトの構成を簡単に復元できます。

  1. 名前を変更します:default.conf:
# mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak
  1. jwt-test.confという名前の新しい設定ファイルを/etc/nginx/conf.d/に作成し、以下の内容を記述します。この設定では、JWT固有のログ記録、JWT検証、コンテンツベースのルーティングを構成します(コードスニペットの後に詳細な分析を記載しています)。
server {
    listen       80;
    access_log  /var/log/nginx/host.access.log jwt;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;

        # JWT validation
        auth_jwt "JWT Test Realm" token=$arg_myjwt;
        auth_jwt_key_file /etc/nginx/api_secret.jwk;
        error_log /var/log/nginx/host.jwt.error.log debug;

        if ( $jwt_claim_uid = 222 ) {
            add_header X-jwt-claim-uid "$jwt_claim_uid" always;
            add_header X-jwt-status "Redirect to $jwt_claim_url" always;
            return 301 $jwt_claim_url;
        }

        if ( $jwt_claim_uid != 222 ) {
            add_header X-jwt-claim-uid "$jwt_claim_uid" always;
            add_header X-jwt-status "Invalid user, no redirect" always;
        }
    }
}

The directives in the locationブロック内のディレクティブは、JWTを含むHTTPリクエストを処理する方法をNGINX Plusに指示します。(access_logディレクティブで定義されるログ設定については、次のセクションを参照してください。)NGINX Plusは次の手順を実行します。

  1. リクエスト文字列のmyjwt引数からJWTを抽出します(auth_jwtディレクティブのtoken引数で指定)。
  2. 次に、auth_jwt_key-fileディレクティブで指定された署名鍵(ここではapi_secret.jwk)を使用してJWTをデコードし、ペイロードに対して以下の処理を行います(これらの処理はJWT処理に固有のものであり、対応するNGINX Plusディレクティブはありません)。
    • JWTの有効期限が切れていないこと、つまり、ペイロード内のexpクレームで指定された有効期限が過去の日時ではないことを検証します。
    • ペイロード内の各クレームについてキーと値のペアを作成します。キー名は、$jwt_claim_claim-name形式の変数です(たとえば、$jwt_claim_uidはuidクレームに対応します)。
  3. エラーが発生した場合は、/var/log/nginx/host.jwt.error.logにdebugレベルで記録します。
  4. Tests whether the value of $jwt_claim_uidの値が222であるかどうかをテストし(2つのifディレクティブで指定)、適切なレスポンスをクライアントに送信します。このように、JWT内の情報を使用してコンテンツベースのルーティングを実行します。注:変数の評価にifディレクティブを使用することは、一般にベストプラクティスとは見なされていません。通常は代わりにmapディレクティブを使用することを推奨します。ただし、この簡単な例では、ifディレクティブは意図したとおりに機能します。
    • 値が「222」の場合、NGINX Plusは、JWtの「return」ディレクティブを使用して、クレーム「url」で指定されたURLにクライアントをリダイレクトするレスポンスを送信します。デバッグのため、レスポンスに2つのヘッダー(「add_header」ディレクティブ)を追加します。1つ目はクレーム「uid」の値を取得し、2つ目はクライアントがリダイレクトされたことを記録します。
    • 値が「222」でない場合、NGINX Plusはデフォルトのインデックスページを返します(同じ「root」ブロック内の「index」ディレクティブと「location」ディレクティブで定義)。ここでもデバッグのため、「uid」クレームの値を取得し、クライアントがJWTで指定されたURLにアクセスできなかったことを記録するヘッダーを追加します。

実質的に、この設定では、許可されたユーザーのみが保護対象のリソースにアクセスできます。つまり、有効なJWTを持つユーザーはJWTで指定されたURLにアクセスでき、有効なJWTを持たないユーザーはデフォルトページにアクセスできます。

JWTデータのログ記録

「jwt」というログ形式を定義して、コンテンツベースルーティング用のJWT処理設定を完了します。このログ形式はjwt-test.confのaccess_logディレクティブから参照され、JWTデータをアクセスログに記録します。

  1. 次の「log_format」ディレクティブを「/etc/nginx/nginx.conf」に追加します:
log_format jwt '$remote_addr - $remote_user [$time_local] "$request" '               '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
               '$jwt_header_alg $jwt_claim_uid $jwt_claim_url';
  1. この形式には、このチュートリアルで使用する2つのJWTクレーム(uidとurl)が含まれていますが、任意のJWTクレームデータを、そのクレームに対応する変数名を$jwt_claim_claim‑nameの形式で指定してログに記録できます。
  2. Save nginx.confを保存し、次のコマンドを実行して、新しいjwt-test.confファイルを含む構成全体の構文が有効かどうかをテストします。エラーが報告された場合は修正してください。
# nginx -t
  1. NGINX Plusを再読み込みします。
# nginx -s reload

設定のテスト

ブラウザーまたはcurlなどのCLIツールを使用して、NGINX PlusがJWTを正しく検証し、そのJWTを提示したクライアントを認証して、コンテンツベースのルーティングを実行していることをテストできます。(コンテンツベースのルーティングを除き、認証と検証のみをテストするには、jwt-test.conf内の2つのifブロックをコメントアウトします。)

テストを実行するため、リクエストURLにmyjwt引数を含め、urlクレームが222であるJWTの全文を指定します。ここでも、表示上の都合により改行を追加しています。

http://example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiO
jE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW
1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9
lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5le
GFtcGxlLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4

対応するcurlコマンドは次のとおりです(必要に応じてコピー&ペーストできるよう、改行は入れていません)。

# curl -v example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4

JWTのuidクレームの値が222であるため、NGINX Plusには制限付きページ(http://secure.example.com)の内容が表示されるはずです。

次に、JWTの「url」クレームの値が「222」でない場合、NGINX Plusが制限付きページの内容を表示せず、代わりにローカルサーバーの「index.html」ページ(この場合は「http://example.com/index.html」)を表示することをテストで確認します。

まず、jwt.ioで、uidクレームが222以外の別のJWTを生成します。ここでは例として111に設定します。そのJWTを使用したリクエストURLは次のとおりです。

http://example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiO
jE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW
1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9
lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5l
eGFtcGxlLmNvbSJ9.Ch9xqsGzB8fRVX-3CBuCxP1Ia3oGKB1OnO6qwi_oBgg

The curlコマンドは次のとおりです。

# curl -v example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.Ch9xqsGzB8fRVX-3CBuCxP1Ia3oGKB1OnO6qwi_oBgg

この場合、NGINX Plusは「http://example.com/index.html」を配信すると想定されます。

どちらのテスト条件でも、ヘッダー検査ツール(curlや一部のブラウザーに付属する開発者ツールなど)を使用して、新しいヘッダーであるX-jwt-claim-uidとX-jwt-statusがレスポンスに追加されたことを確認できます。

テスト中に問題が発生した場合は、次の場所にあるアクセスログとエラーログを確認してください:/var/log/nginx/host.jwt*。特にエラーログでは、検証や検証ファイルへのアクセスなどに関する問題を確認できます。

CookieでのJWTの受け渡し

基本的な例では、NGINX PlusはリクエストURLのmyjwt引数からJWTを抽出します。NGINX Plusでは、CookieでJWTを渡すこともできます(詳細については、NGINX JWTのリファレンスドキュメントを参照してください)。jwt-test.confで、auth_jwtディレクティブを変更し、tokenパラメーターの最初の要素を$argではなく$cookieにします。

auth_jwt "JWT Test Realm" token=$cookie_myjwt;

JWTをmyjwtという名前のCookieで指定する場合、適切なcurlコマンドは次のとおりです。

# curl -v --cookie myjwt=JWT-text example.com/index.html

JWTを使用したコンテンツベースルーティングをぜひお試しください。今すぐNGINXの30日間無料トライアルを開始するか、ユースケースについてお問い合わせください。


共有

著者について

Alan Murphy
Alan Murphyシニアプロダクトマネージャー

Alan Murphyのその他のブログ記事

関連するブログ記事

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
NGINX | 2024/01/18

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する

OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
NGINX | 2023/05/31

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正

Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
NGINX | 2023/02/09

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法

Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINX | 2022/05/24

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング

NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
NGINX | 2018/08/17

SELinuxでNGINXおよびNGINX Plusを使用する

Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。

NGINX PlusによるAWS Auto Scalingグループの負荷分散
NGINX | 2017/03/06

NGINX PlusによるAWS Auto Scalingグループの負荷分散

新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります