APIネットワーク

NGINX Plusで OAuth2.0 のアクセストークンを検証をする その2

こんにちは、narai です。
今回は API の認可で広く活用される OAuth2.0 のアクセストークン検証を NGINX Plus で実施する方法の第2弾となります。

はじめに

前回のブログ で紹介した通り、NGINX Plus では、アクセストークンの検証を行うことができます。
これにより、NGINX Plus に API の認証をオフロードできるだけでなく、様々な機能によりアプリケーションの負荷を軽減しつつ、セキュリティレベルを高めることができるようになります。

今回はそんな NGINX Plus でアクセストークンの検証を行うための具体的な設定を紹介します。

NGINX Plus によるトークン検証の仕組み

前回お伝えした通り、アクセストークンの検証には以下の二つのパターンがあります。

  1. NGINX Plus にてアクセストークンを直接検証する
  2. 認可サーバーへ問い合わせて検証する

この二つのパターンについて、NGINX Plus で検証を行う際の特徴と利用シーンをまとめてみました。

検証方法 メリット デメリット 利用シーン
アクセストークンの直接検証 NGINX Plus 単独で処理するため高速
NGINX Plus での導入が簡単
認可サーバーが停止しても検証可能
トークンの失効に対応できない
アクセストークンが JWT 形式の必要がある
大量のリクエスト処理などハイパフォーマンスを求める
認可サーバーへの問い合わせ 認可サーバーのポリシーを即座に反映できる(失効やスコープ制御など)
クライアントに知られたくない情報を紐づけられる
都度、API アクセスが発生するため、ボトルネックになりやすい(キャッシュを利用することである程度軽減可能)
NJS モジュールを利用したスクリプトが必要になる
アクセストークンに含まれる情報(スコープ)を柔軟に扱いたい

このように、NGINX Plus で検証を行う場合、アクセストークンを直接検証する方法は「導入が簡単で処理が高速」というメリットがありますが、アクセストークンが JWT 形式である必要があります。
一方、認可サーバーへ問い合わせを行う方法では「トークンの失効や柔軟な制御が可能」というメリットがありますが、NJS モジュールが必要となります。
そのため、どちらを利用するかは要件や環境に合わせて選択いただければと思います。
なお、NGINX Plus ではパス(location)毎に検証方法を指定できるため、両方を使い分けることもできます。

それでは、具体的な設定方法を見て行くわけですが、今回のブログでは アクセストークンの直接検証の設定を紹介します。
認可サーバーへ問い合わせる方法は、次回とさせてください。

アクセストークンを直接検証するための設定

NGINX Plus はネイティブに JWT を扱うことができますので、auth_jwt ディレクティブの設定を行うことで、アクセストークンを直接検証することが可能となります。また、デフォルトでは API リクエストの Authorization: Bearer ヘッダーの値をアクセストークンとして精査します。

実際の動作イメージは以下のようになります。

アクセストークンを直接検証するための準備

アクセストークンに JWT を使用して直接検証する場合、JWK (Json Web Key) が必要となります。
JWT は、ヘッダー、ペイロード、署名 で構成されており、署名を検証することでペイロードの改ざんを検知することができますが、この署名の検証に使用するのが JWK となります。
JWK は認可サーバーからダウンロードするか、認可サーバーの公開エンドポイント(jwks_url)から取得することができます。

設定例

実際の設定を見てみましょう。
下記は JWK を認可サーバーからダウンロードして使用する場合の設定であり、別途 conf.d ディレクトリ配下に JWK ファイル (keycloak.jwk) を保存しています。

server {
    listen 80;

    location /api {
        auth_jwt        "internal API";            ## JWT の検証を有効にします
        auth_jwt_type    signed;                   ## 検証する JWT の種類を指定します(signed は平文)
        auth_jwt_key_file   conf.d/keycloak.jwk;   ## 認可サーバーからダウンロードした JWK ファイルの場所を指定します

        proxy_pass http://api_server;
    }

このように auth_jwt ディレクティブで JWT の検証を有効にし、auth_jwt_key_file ディレクティブで署名の検証に必要な JWK ファイルの場所を指定します。また、auth_jwt_type ディレクティブにて JWT の Type を指定しますが、NGINX Plus では平文だけでなく 暗号化された JWT (JWEや Nested JWT)にも対応しています。

これだけで、NGINX Plus でアクセストークンの直接検証が可能になります。

また、JWK を認可サーバーの公開エンドポイント(jwks_url)から取得する場合、以下のように auth_jwt_key_request ディレクティブを使用します。
このディレクティブはサブリクエストを発生させるため、内部パスを使用して url を指定します。

    location /api {
        auth_jwt        "internal API";            ## JWT の検証を有効にします
        auth_jwt_type    signed;                   ## 検証する JWT の種類を指定します
        auth_jwt_key_request /_jwks_uri;           ## サブリクエストのパスを指定
        auth_jwt_key_cache   1h;                   ## JWK のキャッシュ時間

        proxy_pass http://api_server;
    }

    location /_jwks_uri {                         ## 公開エンドポイントへのサブリクエスト
        internal;
        proxy_pass https://<jwks_url>;            ## 認可サーバーの公開エンドポイントを指定
    }

認可サーバーの公開エンドポイントを指定する場合、auth_jwt_key_cache ディレクティブを使用して JWK をキャッシュすることで認可サーバーへのアクセス集中を軽減することができます。

有効/無効 の判断

この設定では、以下の条件が満たされた場合にアクセストークンが有効であると判断します。

① JWT が改ざんされていないこと
② JWT の exp および nbf クレームで指定された有効期限内であること

このように、NGINX Plus を利用すると、アクセストークンの署名を直接検証することが簡単にできます。また、ペイロードのスコープを変数として利用できるため、アクセス制限やアプリに必要な情報を渡すことも簡単にできます。

動作確認

では、実際の動作を見てみましょう。

まず、アクセストークンを用意します。

"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiLUVUd3RaTjhXMWVXQTUwQ2p2ZFZ6R0pkM3o3bmxJMmtnMkVoV01CNk1JIn0.eyJleHAiOjE3NDYyMDk0OTgsImlhdCI6MTc0NjE3MzUxNywiYXV0aF90aW1lIjoxNzQ2MTczNDk4LCJqdGkiOiIxM2Q5OWFlOC1kYjRlLTRkZmQtOTI5Ny01OTYxMTU2ZGFlNWQiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDoxODQ0My9yZWFsbXMvbmFyYWkiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMDRhYTA3NTItNjIzNC00ZGY4LThkNDctNjBlYmFjOWM2MDBkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoid2ViLWFwcCIsInNlc3Npb25fc3RhdGUiOiJlNTMxMmE4My0xNjU0LTQ5OTMtYjU1NC0xM2Q4ODgwZDQ2YzkiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MTMwMDAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtbmFyYWkiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsid2ViLWFwcCI6eyJyb2xlcyI6WyJ3ZWItYXBwIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwic2lkIjoiZTUzMTJhODMtMTY1NC00OTkzLWI1NTQtMTNkODg4MGQ0NmM5IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidXNlcjEiLCJnaXZlbl9uYW1lIjoidGVzdCIsImZhbWlseV9uYW1lIjoidXNlciIsInVzZXJpZCI6IjAwMDAxIiwiZW1haWwiOiJ1c2VyMUBsb2NhbC5jb20ifQ.hBfRg7HiU8klHsnFMm5FL5uuMr-s1D7jV-KGHjJHtcMztuKVbApihJrKLa0zlDDm5M2Sjstlhs2njnYH6-SYofUOeGk-9DZQnXV-uR1lxErESAMTdAfUOpXVr3VWakBF378wJSmqJAKlN8SUo0HUnDvlkGeLKlZswuLauDgJvLIGGswU9LmBIN53NrSoHNe9r8LXFqeTPCfrRdS3eSh1DuGbWUeV3q-j7T_p9ncq38aY3L0Y5S1sWeeEW9GwxZ6C881osJhwB0f23X24jG_2a4j2QDa84SOknkkuuKOMbzhIE3cHBreTnC_o2V3GBSEejoNO2oGOsXDsslSUqI3NEQ"

アクセストークンの中身(ペイロード)はこのような内容となっています。
このトークンの有効期限(exp)は 2025/5/2 18:00 となっています。

{
  "exp": 1746209498,
  "iat": 1746173517,
  "auth_time": 1746173498,
  "jti": "13d99ae8-db4e-4dfd-9297-5961156dae5d",
  "iss": "https://localhost:18443/realms/narai",
  <中略>
  "given_name": "test",
  "family_name": "user",
  "userid": "00001",
  "email": "user1@local.com"
}

では、このアクセストークンを使ってリクエストを投げてみましょう。

まずはアクセストークンがない状態でリクエストを投げてみます。

# curl http://localhost:12080/api/9/nginx

HTTP/1.1 401 Unauthorized
Server: nginx/1.27.2
Date: Fri, 02 May 2025 08:45:45 GMT
Content-Type: text/html
Content-Length: 179
Connection: close
WWW-Authenticate: Bearer realm="internal API"

<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.27.2</center>
</body>
</html>

アクセストークンがない場合は 401 が返ってきて認証に失敗したことがわかります。

次に、アクセストークンを持たせてリクエストを投げてみます。
API リクエストに Authorization ヘッダーを追加してアクセストークンを付与します。
また、アクセストークンの有効期限内でリクエストを送信しています。

# curl http://localhost:12080/api/9/nginx -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR…"

HTTP/1.1 200 OK
Server: nginx/1.27.2
Date: Fri, 02 May 2025 08:44:22 GMT
Expires: Thu, 01 Jan 1970 00:00:01 GMT

{
  "version": "1.27.2",
  "build": "nginx-plus-r33",
  "address": "172.18.0.2",
  "pid": 401,
  "ppid": 1
}

今度は情報が取得できました。
このように、NGINX Plus を使うとアクセストークンを使用した認可制御が簡単に実現できます。

まとめ

アクセストークンの検証をリソースサーバーから NGINX Plus にオフロードすることで、すべてのリクエストを NGINX Plusでフィルタリングすることができるようになります。また、暗号化された JWT (JWE や Nested JWT)の復号も可能なため、負荷のかかる処理は NGINX Plus にお任せして、必要な情報だけをリソースサーバーに提供することが可能です。

このように、NGINX Plus を使うと、OAuth2.0 との連携が簡単に構成できるようになるだけでなく、セキュリティの強化やアプリ開発の工数削減にもつながりますので、アクセストークンの検証を考えている方は NGINX Plus を検討してみてください。

この記事に関連する製品・サービス

この記事に関連する記事