Nginx CAS 認証と SSO: Casdoor で Nginx 背後のあらゆるアプリを保護
オープンソースの nginx には CAS モジュールがないため、「nginx CAS 認証」は実際には 2 つのセットアップのいずれかを意味します。nginx が auth_request モジュールを通じてすべてのリクエストについて認証サービスに問い合わせ、Casdoor が応答する場合(直接 /api/forward-auth で、または casdoor-forward-auth や OAuth2 Proxy 経由)、あるいは nginx 背後のアプリ自体が CAS クライアントであり、Casdoor の CAS サーバーエンドポイントと通信する場合です。nginx を再構築できる場合は、Casdoor nginx モジュールが nginx 内部でログインを実行し、nginx を実行したくない場合は、Casdoor のサイトプロキシをアプリの前に配置できます。どの場合も、ログインページは Casdoor のものなので、1 つの SSO セッション、1 組の権限、1 つの MFA ポリシーですべてのアプリをカバーします。
オプションの概要
| オプション | ログインロジックの実行場所 | nginx に必要なもの | Casdoor とのプロトコル | アクセス許可されるユーザー | 選択するタイミング |
|---|---|---|---|---|---|
| Casdoor forward auth | Casdoor, /api/forward-auth | auth_request | アプリドメイン上の Casdoor セッション Cookie | アプリケーション権限、少なくとも 1 分ごとに再チェック | nginx が既に前面にあり、余計なサービスを追加したくない場合 |
| casdoor-forward-auth | 小さな Go サービス | auth_request | OIDC 認可コード | Casdoor のロールまたはグループ、ロケーションごと | 多くのサブドメインで共有される 1 つのセッション Cookie が必要な場合 |
| OAuth2 Proxy | OAuth2 Proxy | auth_request | OIDC | allowed_groups、メールドメイン | 既に OAuth2 Proxy を運用している場合 |
| casdoor-nginx-module | nginx そのもの | コンパイル済みモジュール | PKCE を使用した OIDC、ローカルで検証されたトークン | アプリのサインイン済みユーザーなら誰でも | 追加プロセスなし、さらに API 用のベアラートークンが必要な場合 |
| アプリ内の CAS クライアント | アプリ | なし、単にプロキシするだけ | CAS 1.0, 2.0, 3.0 | アプリが属性をどう扱うかによる | アプリが既に CAS をサポートしている場合 |
| Casdoor site proxy | Casdoor (ポート 80 および 443) | nginx なし | Casdoor セッション | アプリケーション権限 | アプリの前に他の何も存在しない場合 |
最初の 3 つの違いは、nginx のサブリクエストに応答するのが誰であるかだけです。
「nginx CAS モジュール」が存在しない理由
CAS はアプリケーションと CAS サーバー間の会話です。アプリはブラウザをサーバーの /login に service URL で送信し、その URL でサービスチケットを受け取り、サーバー間呼び出しでチケットを検証します。Apache httpd には、Web サーバーレベルでこれを行うための Apereo の mod_auth_cas がありますが、nginx の公式モジュールには CAS モジュールがありません。nginx の場合、標準的な構成要素は ngx_http_auth_request_module です。リクエストを提供する前に、nginx はあなたが選んだ URL へサブリクエストを送信します。2xx の応答はリクエストを通し、401 または 403 はそのコードで拒否し、他のコードはエラーとなります。
ユーザーにとって、CAS ログインと OIDC ログインは同じように見えます。Casdoor のログインページへのリダイレクトと戻りです。したがって、CAS サポートのないアプリの場合、実用的な答えは nginx レベルでの OIDC です。プロトコルの背景情報: What is CAS。
auth_request モジュールはデフォルトでは組み込まれていません。コンパイル時に --with-http_auth_request_module を追加するか、パッケージ化された nginx で nginx -V を確認してください。
オプション 1: Casdoor 独自の forward auth
Casdoor は nginx のサブリクエスト自体に応答できます。Casdoor に Site を追加し、その Domain をアプリのドメインに、Casdoor app をユーザーがサインインするアプリケーションに設定し、そのアプリケーションの Redirect URLs に https://app.example.com/caswaf-handler を追加します。トラフィックは Casdoor を通過しないため、ホストとポートは空のままにできます。
auth_request はリダイレクトを追跡できないため、Casdoor は未認証のサブリクエストに対して 401 で応答し、ログイン URL を Location に設定します。nginx はこれを 302 に変換します:
server {
server_name app.example.com;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
location = /caswaf-handler {
proxy_pass http://casdoor:8000/api/forward-auth;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Uri $request_uri;
}
location = /casdoor-auth {
internal;
proxy_pass http://casdoor:8000/api/forward-auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Forwarded-Method $request_method;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
auth_request /casdoor-auth;
auth_request_set $casdoor_redirect $upstream_http_location;
auth_request_set $casdoor_user $upstream_http_x_forwarded_user;
auth_request_set $casdoor_email $upstream_http_x_forwarded_email;
auth_request_set $casdoor_groups $upstream_http_x_forwarded_groups;
error_page 401 =302 $casdoor_redirect;
proxy_set_header X-Forwarded-User $casdoor_user;
proxy_set_header X-Forwarded-Email $casdoor_email;
proxy_set_header X-Forwarded-Groups $casdoor_groups;
proxy_pass http://app:3000;
}
}
バックエンドは X-Forwarded-User、X-Forwarded-Email、および X-Forwarded-Groups でユーザーを受信します。アクセスはアプリケーションに対する permission に従い、少なくとも 1 分ごとに再チェックされるため、無効化されたユーザーはすぐにアクセスを失います。サイトの Public paths(webhooks、ヘルスチェック)はログインなしで通過します。
参照として Using your own reverse proxy を、Grafana のウォークスルーとして Add login to an app without OIDC をご覧ください。アプリは nginx 経由でのみアクセス可能にする必要があります。さもないと、誰でも X-Forwarded-User を自分で送信できてしまいます。
オプション 2: casdoor-forward-auth または OAuth2 Proxy
各アプリ用に Casdoor サイトを作成したくない場合は、Casdoor に対して OIDC フローを実行しサブリクエストに応答する小さなサービスを実行してください。
casdoor-forward-auth は、このための Casdoor のスタンドアロンサービスです(traefik-casdoor-auth を置き換えました)。サーバー状態を持たず、ユーザーを署名付きクッキーに保持します。nginx 向けには /verify を公開しており、ID ヘッダー付きで 200 を返すか 401 を返します。nginx は 401 でその /login にリダイレクトします:
server {
server_name app.example.com;
location = /_casdoor_verify {
internal;
proxy_pass http://casdoor-forward-auth:9999/verify?groups=built-in/dev;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
location @casdoor_login {
return 302 https://auth.example.com/login?rd=$scheme://$http_host$request_uri;
}
location / {
auth_request /_casdoor_verify;
error_page 401 = @casdoor_login;
auth_request_set $casdoor_user $upstream_http_x_forwarded_user;
auth_request_set $casdoor_roles $upstream_http_x_forwarded_roles;
proxy_set_header X-Forwarded-User $casdoor_user;
proxy_set_header X-Forwarded-Roles $casdoor_roles;
proxy_pass http://app:8080;
}
}
verify URL の roles および groups クエリパラメータにより、各アプリを Casdoor ロールの保持者またはグループのメンバーに制限できます。それらを持たないサインイン済みユーザーは 403 を受けます。ロールとグループはログイン時に読み込まれセッション中に保持されるため、変更はユーザーが再度サインインした後で適用されます。COOKIE_DOMAIN=example.com を設定すると、1 つのログインですべてのサブドメインをカバーできます。同じサービスを Traefik 向けに解説しているのが Traefik guide です。
OAuth2 Proxy も同様に機能し、provider = "oidc" と oidc_issuer_url を Casdoor URL に設定します。その /oauth2/auth エンドポイントは 202 または 401 を返し、user と email ヘッダーには --set-xauthrequest が必要です。また、その nginx 例における転送ヘッダー処理には --reverse-proxy が必要です。重要な Casdoor 設定が 2 つあります。JWT-Standard トークン形式を使用してください。そうしないと未検証のメールアドレスを持つユーザーが拒否されます。また、profile スコープを保持してグループとロールが返されるようにしてください。OAuth2 Proxy with Casdoor と nginx community edition guide を参照してください。
オプション 3: Casdoor nginx モジュール
casdoor-nginx-module は、別のプロセスなしに nginx 内でログインを行います。セッションがないブラウザは Casdoor のログインページに移動します。モジュールは PKCE でコードを交換し、Casdoor 証明書に対してアクセストークンの署名、exp、aud、タイプを検証し、ユーザーを署名付きクッキーに保持します。API クライアントは Casdoor アクセストークンを伴う Authorization: Bearer を送信でき、Casdoor への呼び出しなしにローカルで検証されます。
これには nginx 1.22 以上と OpenSSL 1.1.1 以上が必要です。既に実行している nginx に対して動的モジュールとしてビルドできます:
./configure --with-compat --with-http_ssl_module --add-dynamic-module=/path/to/casdoor-nginx-module
make modules
cp objs/ngx_http_casdoor_module.so /etc/nginx/modules/
load_module modules/ngx_http_casdoor_module.so;
events {}
http {
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/nginx/app.crt;
ssl_certificate_key /etc/nginx/app.key;
casdoor_endpoint https://door.example.com;
casdoor_client_id <client-id>;
casdoor_client_secret <client-secret>;
casdoor_certificate /etc/nginx/casdoor.pem;
casdoor_redirect_uri https://app.example.com/casdoor/callback;
casdoor_token_location /_casdoor/token;
location = /_casdoor/token {
internal;
proxy_pass https://door.example.com/api/login/oauth/access_token;
proxy_ssl_server_name on;
}
location = /casdoor/callback {
casdoor_callback;
}
location / {
casdoor on;
proxy_set_header X-User $casdoor_user;
proxy_pass http://127.0.0.1:3000;
}
}
}
プレースホルダーをアプリの値に置き換えてください。セッションは期限切れになるまで Casdoor で再チェックされないため、ユーザーの無効化はセッション終了時に有効になります。casdoor_session_timeout はそのウィンドウを短縮します。このモジュールにはロールやグループのチェックがありません。NGINX Plus では、代わりに the NGINX Plus guide を参照してください。
オプション 4: アプリが CAS クライアントである場合
アプリがすでに CAS をサポートしている場合(例えば Apereo CAS クライアントを使用した Java アプリなど)、nginx は単にプロキシし、Casdoor が CAS サーバーとなります。各 Casdoor アプリケーションには /cas/<organization>/<application> の下に CAS エンドポイントがあります: /login と /logout、CAS 1.0 用の /validate、2.0 用の /serviceValidate、/proxyValidate、/proxy、3.0 用の /p3/serviceValidate と /p3/proxyValidate、そして SAML 1.1 検証用の /samlValidate。
アプリはブラウザを /login?service=... に送り、Casdoor は ticket=ST-... を付けて戻り、アプリはそれを検証します:
curl "https://door.example.com/cas/my-org/my-app/p3/serviceValidate?ticket=ST-1234567890&service=https%3A%2F%2Fapp.example.com%2F"
成功時の応答は cas:user と email、表示名、memberOf などの属性を含む cas:serviceResponse です。JSON を得るために format=json を追加してください。よくある落とし穴:
- すべての
serviceURL はアプリの Redirect URLs にリストされている必要があり、検証時のserviceはチケットが発行されたものと完全に一致する必要があります。 - チケットは一度だけ有効で、5 分後に期限切れになります。
- チケットはメモリ内に保持されるため、複数の Casdoor レプリカがある場合、検証はチケットを発行したレプリカに到達する必要があります(スティッキーセッション)。
- nginx の後ろでは、
HostとX-Forwarded-Protoを渡して、アプリがブラウザが見たものと同じserviceURL を構築するようにしてください。
Java クライアントの各 CAS バージョン用のフィルタを含むセットアップについては、Connect a CAS client を参照してください。
オプション 5: Casdoor をリバースプロキシにする
アプリの前に何も存在しない場合、Casdoor は直接サーブできます。サイトが存在すれば、そのサイトプロキシは gatewayHttpPort と gatewayHttpsPort(デフォルトでは 80 と 443)で待ち受けます。サイトの Host をバックエンドに設定し、証明書を選択し、Casdoor app を設定し、DNS を Casdoor に向けます。ログイン、権限、ヘッダーの動作はオプション 1 と同じです。Sites を参照してください。
MFA の追加
MFA には nginx やアプリの変更は不要です。Casdoor は OAuth 認可コードや CAS サービスチケットを発行する前に、独自のサインインページで第二要素を要求するため、上記のすべてのオプションで利用可能です。ユーザーは TOTP アプリ、SMS、メール、RADIUS をリカバリコードとともに使用でき、管理者は MFA items を使って組織全体で MFA を必須にできます。これは「CAS 2FA」にも回答します。Casdoor を CAS サーバーとして使用する場合、第二要素は CAS ログインの一部となります。
MFA はログイン時に強制され、リクエストごとではありません。nginx や forward-auth サービスがすでに保持しているセッションは期限切れまで続き、組織の MFA remember time により、信頼できるデバイスで一時的に第二要素をスキップできます。MFA guide と What is MFA を参照してください。
Casdoor にサインインすると、ユーザーは Casdoor に導く他のアプリに新しいパスワード入力を求められずにアクセスできます。Auto signin を使用すると、Casdoor はアカウントピッカーもスキップします。single sign-on と Single logout を参照してください。
どれを選ぶべきか
- アプリにログインがなく、nginx がすでに前面にある場合: Casdoor の forward auth。
- 複数のサブドメインで単一のログインを実現する場合や、場所ごとのロールチェックを行う場合: casdoor-forward-auth (既に利用している場合は OAuth2 Proxy)
- nginx を自前で構築し、API もベアラートークンで保護する場合: casdoor-nginx-module
- アプリが CAS に対応している場合: Casdoor の CAS エンドポイント、nginx は単純なプロキシとして使用
- まだプロキシを導入していない場合: Casdoor のサイトプロキシ
参考資料
- nginx: ngx_http_auth_request_module
- OAuth2 Proxy: nginx integration
- Apereo: CAS Protocol Specification, mod_auth_cas
- Casdoor: casdoor-forward-auth, casdoor-nginx-module
- Casdoor docs: Sites, Connect a CAS client, MFA