私は同様の問題に取り組んでいます。ユーザーは、すべてのリクエストに対して認証を受ける必要があります。私はユーザーをバックエンドアプリで少なくとも1回認証すること(JWTトークンの検証)に焦点を当ててきましたが、その後、バックエンドはもう必要ないと判断しました。
私は、デフォルトで含まれていないNginxプラグインを必要としないようにしました。それ以外の場合は、nginx-jwtまたはLuaスクリプトをチェックできます。これらはおそらく優れたソリューションです。
アドレッシング認証
これまでのところ、私は次のことを行いました:
を使用して認証をNginxに委任しましたauth_request
。これは、internal
リクエストをバックエンドトークン検証エンドポイントに渡す場所を呼び出します。これだけでは、多数の検証を処理する問題にまったく対処できません。
トークン検証の結果は、proxy_cache_key "$cookie_token";
ディレクティブを使用してキャッシュされます。トークンの検証が成功すると、バックエンドは、Cache-Control
最大5分間だけトークンをキャッシュするようにNginxに指示するディレクティブを追加します。この時点で、一度検証された認証トークンはキャッシュにあります。同じユーザー/トークンからの後続のリクエストは、認証バックエンドに影響しなくなります。
無効なトークンによる潜在的なフラッディングからバックエンドアプリを保護するために、バックエンドエンドポイントが401を返したときに拒否された検証もキャッシュします。これらは、このような要求でNginxキャッシュがいっぱいになる可能性を回避するために、短時間だけキャッシュされます。
401(これもNginxによってキャッシュされます)を返すことでトークンを無効にするログアウトエンドポイントなど、いくつかの追加の改善点を追加しました。これにより、ユーザーがログアウトをクリックした場合、期限が切れていなくてもトークンを使用できなくなります。
また、私のNginxキャッシュには、すべてのトークンについて、関連付けられたユーザーがJSONオブジェクトとして含まれているため、この情報が必要な場合にDBから取得する必要がありません。また、トークンを復号化する必要もありません。
トークンの有効期間と更新トークンについて
5分後、トークンはキャッシュ内で期限切れになるため、バックエンドに再度クエリが実行されます。これは、ユーザーがログアウトしたり、侵害されたりしたため、トークンを無効化できるようにするためです。このような定期的な再検証と、バックエンドでの適切な実装により、リフレッシュトークンを使用する必要がなくなります。
従来、更新トークンは新しいアクセストークンを要求するために使用されていました。それらはバックエンドに保存され、アクセストークンのリクエストが、この特定のユーザーのデータベースにあるものと一致する更新トークンで行われることを確認します。ユーザーがログアウトした場合、またはトークンが危険にさらされた場合は、DB内の更新トークンを削除または無効化して、無効化された更新トークンを使用する新しいトークンの次のリクエストが失敗するようにします。
つまり、更新トークンは通常長い有効期間を持ち、常にバックエンドに対してチェックされます。これらは、有効期間が非常に短い(数分)アクセストークンを生成するために使用されます。これらのアクセストークンは通常、バックエンドに到達しますが、署名と有効期限のみを確認します。
ここでの設定では、アクセストークンと更新トークンの両方と同じ役割と機能を持つ、有効期間がより長い(数時間または1日の可能性がある)トークンを使用しています。検証と無効化はNginxによってキャッシュされているため、5分ごとに1回だけバックエンドによって完全に検証されます。したがって、複雑さを増すことなく、更新トークンを使用する(トークンをすばやく無効化できる)利点を維持します。また、署名と有効期限の確認のみに使用されている場合でも、単純な検証がNginxキャッシュよりも少なくとも1桁遅いバックエンドに到達することは決してありません。
この設定では、すべての着信リクエストがauth_request
Nginxディレクティブに到達する前にそれに到達するため、バックエンドで認証を無効にすることができます。
リソースごとの承認を実行する必要がある場合、問題は完全には解決しませんが、少なくとも基本的な承認部分は保存しました。また、Nginxのキャッシュされた認証応答にデータが含まれ、それをバックエンドに返すことができるため、トークンの復号化を回避したり、DBルックアップを行ってトークンデータにアクセスしたりすることもできます。
今、私の最大の懸念は、セキュリティに関連する明らかな何かを、それを実現せずに壊してしまう可能性があることです。つまり、受信したトークンは、Nginxによってキャッシュされる前に、少なくとも1回は検証されます。強化されたトークンはすべて異なり、キャッシュキーも異なるため、キャッシュにヒットしません。
また、実際の認証では、追加のナンスまたは何かを生成(および検証)することで、トークン盗用と戦うことにも言及する価値があります。
これが私のアプリのNginx設定の簡単な抜粋です。
# Cache for internal auth checks
proxy_cache_path /usr/local/var/nginx/cache/auth levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=10m use_temp_path=off;
# Cache for content
proxy_cache_path /usr/local/var/nginx/cache/resx levels=1:2 keys_zone=content_cache:16m max_size=128m inactive=5m use_temp_path=off;
server {
listen 443 ssl http2;
server_name ........;
include /usr/local/etc/nginx/include-auth-internal.conf;
location /api/v1 {
# Auth magic happens here
auth_request /auth;
auth_request_set $user $upstream_http_X_User_Id;
auth_request_set $customer $upstream_http_X_Customer_Id;
auth_request_set $permissions $upstream_http_X_Permissions;
# The backend app, once Nginx has performed internal auth.
proxy_pass http://127.0.0.1:5000;
proxy_set_header X-User-Id $user;
proxy_set_header X-Customer-Id $customer;
proxy_set_header X-Permissions $permissions;
# Cache content
proxy_cache content_cache;
proxy_cache_key "$request_method-$request_uri";
}
location /api/v1/Logout {
auth_request /auth/logout;
}
}
次に、/auth
上記のように含まれている内部エンドポイントの構成抽出を次に示します/usr/local/etc/nginx/include-auth-internal.conf
。
# Called before every request to backend
location = /auth {
internal;
proxy_cache auth_cache;
proxy_cache_methods GET HEAD POST;
proxy_cache_key "$cookie_token";
# Valid tokens cache duration is set by backend returning a properly set Cache-Control header
# Invalid tokens are shortly cached to protect backend but not flood Nginx cache
proxy_cache_valid 401 30s;
# Valid tokens are cached for 5 minutes so we can get the backend to re-validate them from time to time
proxy_cache_valid 200 5m;
proxy_pass http://127.0.0.1:1234/auth/_Internal;
proxy_set_header Host ........;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Accept application/json;
}
# To invalidate a not expired token, use a specific backend endpoint.
# Then we cache the token invalid/401 response itself.
location = /auth/logout {
internal;
proxy_cache auth_cache;
proxy_cache_key "$cookie_token";
# Proper caching duration (> token expire date) set by backend, which will override below default duration
proxy_cache_valid 401 30m;
# A Logout requests forces a cache refresh in order to store a 401 where there was previously a valid authorization
proxy_cache_bypass 1;
# This backend endpoint always returns 401, with a cache header set to the expire date of the token
proxy_pass http://127.0.0.1:1234/auth/_Internal/Logout;
proxy_set_header Host ........;
proxy_pass_request_body off;
}
。
コンテンツ配信への対応
これで、認証がデータから分離されました。すべてのユーザーで同一であると言ったので、コンテンツ自体もNginx(私の例ではcontent_cache
ゾーン)でキャッシュできます。
スケーラビリティ
このシナリオは、Nginxサーバーが1台あると想定して、すぐに機能します。実際のシナリオでは、おそらく高可用性、つまり複数のNginxインスタンスがあり、(Laravel)バックエンドアプリケーションをホストしている可能性もあります。その場合、ユーザーが行うすべてのリクエストは任意のNginxサーバーに送信される可能性があり、すべてのトークンがローカルにキャッシュされるまで、ユーザーはバックエンドに到達してトークンを確認し続けます。サーバーの数が少ない場合でも、このソリューションを使用すると大きなメリットが得られます。
ただし、複数のNginxサーバー(およびキャッシュ)を使用すると、次のようにすべてのトークンキャッシュを(強制的に更新することにより)パージできないため、サーバー側でログアウトできなくなることに注意することが重要です。/auth/logout
私の例ではそうします。5分間のトークンキャッシュ期間のみが残り、バックエンドへのクエリがすぐに強制され、リクエストが拒否されたことがNginxに通知されます。部分的な回避策は、ログアウト時にクライアントのトークンヘッダーまたはCookieを削除することです。
どんなコメントでも大歓迎です!