跳到主内容

Nginx CAS 认证与 SSO:使用 Casdoor 在 Nginx 后保护任意应用

· 阅读时间:一分钟
Yang Luo
Casdoor Maintainer

开源版 nginx 没有 CAS 模块,因此实际上“nginx CAS 认证”意味着两种配置之一。要么 nginx 通过其 auth_request 模块向每个请求询问认证服务,由 Casdoor 回答(直接通过 /api/forward-auth,或通过 casdoor-forward-auth 或 OAuth2 Proxy),要么 nginx 后面的应用本身是一个 CAS 客户端,并与 Casdoor 的 CAS 服务器端点通信。如果您可以重新构建 nginx,Casdoor nginx 模块会在 nginx 内部运行登录;如果您不想运行 nginx,Casdoor 的站点代理可以位于应用之前。在所有情况下,登录页面都是 Casdoor 的,因此一个 SSO 会话、一组权限和一个 MFA 策略覆盖所有应用。

选项概览​

选项登录逻辑运行位置Nginx 需要什么与 Casdoor 的协议谁可以进入何时选择它
Casdoor 转发认证Casdoor, /api/forward-authauth_request应用域名上的 Casdoor 会话 Cookie应用权限,至少每分钟再次检查Nginx 已经在前端,且您不想添加额外服务
casdoor-forward-auth一个小型 Go 服务auth_requestOIDC 授权码Casdoor 角色或组,按 location 区分多个子域名共享一个会话 Cookie
OAuth2 ProxyOAuth2 Proxyauth_requestOIDCallowed_groups, 电子邮件域您已经运行 OAuth2 Proxy
casdoor-nginx-modulenginx 本身一个编译好的模块带有 PKCE 的 OIDC,本地验证令牌该应用的任何已登录用户无需额外进程,并为 API 提供 Bearer 令牌
应用中的 CAS 客户端应用无,仅代理CAS 1.0, 2.0, 3.0应用如何处理属性应用已经支持 CAS
Casdoor 站点代理Casdoor 在端口 80 和 443无 NginxCasdoor 会话应用权限没有其他东西位于应用前端

前三个选项的区别仅在于谁回答 nginx 的子请求。

为什么没有“nginx CAS 模块”​

CAS 是应用程序与 CAS 服务器之间的对话:应用将浏览器发送到服务器的 /login 并附带 service URL,在该 URL 上获得服务票据,并通过服务器到服务器的调用验证票据。Apache httpd 有 Apereo 的 mod_auth_cas 来在 Web 服务器上执行此操作,但 nginx 的官方模块中没有 CAS 模块。对于 nginx,标准构建块是 ngx_http_auth_request_module:在提供服务之前,nginx 向您选择的 URL 发送子请求。2xx 响应允许请求通过,401 或 403 以该代码拒绝,其他代码视为错误。

对用户而言,CAS 登录和 OIDC 登录看起来相同:重定向到 Casdoor 登录页面再返回。因此,对于不支持 CAS 的应用,实际解决方案是在 nginx 层使用 OIDC。关于协议的背景信息:什么是 CAS。

auth_request 模块默认不构建:编译时添加 --with-http_auth_request_module,或在打包版的 nginx 上检查 nginx -V。

选项 1:Casdoor 自身的转发认证​

Casdoor 可以自行回答 nginx 的子请求。您在 Casdoor 中添加一个 Site,其 Domain 为应用的域名,其 Casdoor app 为用户登录的应用,并将 https://app.example.com/caswaf-handler 添加到该应用的 Redirect URLs。主机和端口可以留空,因为流量不经过 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 中接收用户。访问遵循应用上的权限,至少每分钟再次检查,因此被禁用的用户会迅速失去访问权限。站点的 Public paths(Webhooks,健康检查)无需登录即可通过。

有关参考信息,请参阅 使用您自己的反向代理,有关 Grafana 的分步指南,请参阅 在不使用 OIDC 的情况下为应用添加登录。该应用必须仅通过 nginx 访问,否则任何人都可以自己发送 X-Forwarded-User。

选项 2:casdoor-forward-auth 或 OAuth2 Proxy​

如果您不想为每个应用创建一个 Casdoor 站点,可以运行一个小型服务,针对 Casdoor 执行 OIDC 流程并响应子请求。

casdoor-forward-auth 是 Casdoor 为此提供的独立服务(它取代了 traefik-casdoor-auth)。它将用户保存在签名 Cookie 中,无需服务器状态。对于 nginx,它暴露 /verify 端点,返回带有身份标头的 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;
}
}

验证 URL 上的 roles 和 groups 查询参数将每个应用限制为持有特定 Casdoor 角色或属于特定群组的用户;已登录但缺少这些权限的用户会收到 403 错误。角色和群组在登录时读取并在会话期间保留,因此更改会在用户重新登录后生效。设置 COOKIE_DOMAIN=example.com 可以让一次登录覆盖所有子域名。Traefik 指南 介绍了如何在 Traefik 中使用相同的服务。

OAuth2 Proxy 的工作原理类似,需将 provider = "oidc" 且 oidc_issuer_url 设置为您的 Casdoor URL。其 /oauth2/auth 端点返回 202 或 401,用户和电子邮件标头需要 --set-xauthrequest,并且其 nginx 示例中的转发标头处理需要 --reverse-proxy。有两个 Casdoor 设置很重要:使用 JWT-Standard 令牌格式,否则未验证邮箱的用户将被拒绝;保留 profile 范围以便返回群组和角色。请参阅 OAuth2 Proxy with Casdoor 和 nginx 社区版指南。

选项 3:Casdoor nginx 模块​

casdoor-nginx-module 在 nginx 内部处理登录,无需单独的进程。没有会话的浏览器会跳转到 Casdoor 的登录页面;模块使用 PKCE 交换代码,根据 Casdoor 证书验证访问令牌的签名、exp、aud 和类型,并将用户保存在签名 Cookie 中。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 上,请参阅 NGINX Plus 指南。

选项 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:serviceResponse,其中包含 cas:user 以及电子邮件、显示名称和 memberOf 等属性;添加 format=json 以获取 JSON 格式。容易让人困惑的地方包括:

  • 每个 service URL 都必须列在应用的 Redirect URLs 中,并且在验证时的 service 必须与颁发票据时使用的完全一致。
  • 票据只能使用一次,并在 5 分钟后过期。
  • 票据保存在内存中,因此在有多个 Casdoor 副本的情况下,验证请求必须到达颁发票据的那个副本(粘性会话)。
  • 在 nginx 后面,传递 Host 和 X-Forwarded-Proto,以便应用构建与浏览器看到的相同的 service URL。

设置方法(包括针对不同 CAS 版本的 Java 客户端过滤器)请参见 连接 CAS 客户端。

选项 5:让 Casdoor 成为反向代理​

当应用前面没有其他组件时,Casdoor 可以直接提供服务:一旦存在站点,其站点代理将在 gatewayHttpPort 和 gatewayHttpsPort(默认为 80 和 443)上监听。将站点的 Host 设置为后端,选择证书,设置 Casdoor app,并将 DNS 指向 Casdoor。登录、权限和标头的行为与选项 1 相同。请参阅 Sites。

添加 MFA​

MFA 不需要对 nginx 或应用进行任何更改。Casdoor 在其自身的登录页面上在发出 OAuth 授权代码或 CAS 服务票据之前询问第二因素,因此上述每个选项都支持它。用户可以使用 TOTP 应用、短信、电子邮件或 RADIUS,并配有恢复代码,管理员可以通过 MFA 项目 要求组织启用 MFA。这也回答了“CAS 2FA”的问题:当 Casdoor 作为 CAS 服务器时,第二因素是 CAS 登录的一部分。

MFA 在登录时强制执行,而不是针对每个请求:nginx 或 forward-auth 服务已经持有的会话将持续到过期为止,组织的 MFA remember time 允许用户在受信任的设备上一段时间内跳过第二因素。请参阅 MFA 指南 和 什么是 MFA。

一旦登录到 Casdoor,用户即可访问其他将请求发送到 Casdoor 的应用,而无需新的密码提示;启用 Auto signin 后,Casdoor 还会跳过账户选择器。请参阅 单点登录 和 Single logout。

如何选择​

  • 应用中无登录功能且 nginx 已在前面:使用 Casdoor 的 forward auth。
  • 跨多个子域名的统一登录,或按位置进行角色检查:casdoor-forward-auth(如果你已经运行 OAuth2 Proxy,也可以使用它)。
  • 你自己构建 nginx 并使用 bearer 令牌保护 API:casdoor-nginx-module。
  • 应用支持 CAS:使用 Casdoor 的 CAS 端点,nginx 作为普通代理。
  • 尚无代理:使用 Casdoor 的网站代理。

参考资料​