跳到主内容

Traefik

casdoor-forward-auth 可以在 Traefik 后面的任何服务前加上 Casdoor 单点登录,无需修改服务本身。 Traefik 通过内置的 forwardAuth 中间件对每个请求询问 casdoor-forward-auth:已登录用户带着请求头中的身份信息访问服务,其他人先被送到 Casdoor 登录页。

The same service also works with Caddy (forward_auth) and Nginx (auth_request).

工作原理​

  1. 对受保护服务的每个请求,Traefik 都会调用 casdoor-forward-auth 的 /auth。
  2. 带有效会话 Cookie 时,/auth 返回 200,并带上 X-Forwarded-User 等请求头,Traefik 会把它们复制到发给你的服务的请求中。
  3. 没有会话时,页面请求会被跳转到 Casdoor。其他请求(POST、PUT……)会得到 401,因为它们无法跟随登录跳转。
  4. 登录后,Casdoor 跳转到 /callback。 casdoor-forward-auth 检查 state、交换授权码、校验访问令牌,把用户保存在签名的 HttpOnly 会话 Cookie 中,然后把用户送回原来请求的页面。

casdoor-forward-auth 在服务器端不保存状态,所以只要共用同一个 Cookie 密钥,就可以运行多个副本。

先决条件​

  • Traefik v2 或 v3
  • 一个 Casdoor 实例(见 服务器安装)
  • 两个指向 Traefik 的主机名,例如 casdoor-forward-auth 用 auth.example.com,受保护的服务用 app.example.com。只有一个主机时,见 使用单个主机。

第 1 步:配置 Casdoor 应用​

  1. 在 Casdoor 中创建或编辑一个应用。

  2. 把 casdoor-forward-auth 的回调地址加到 Redirect URLs:

    https://auth.example.com/callback
  3. 记下 Client ID 和 Client secret。

Casdoor 应用设置

第 2 步:部署 casdoor-forward-auth 和 Traefik​

创建 docker-compose.yml:

services:
traefik:
image: traefik:v3.1
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro

casdoor-forward-auth:
image: ghcr.io/casdoor/casdoor-forward-auth:latest
environment:
CASDOOR_ENDPOINT: https://door.casdoor.com
CLIENT_ID: <client ID>
CLIENT_SECRET: <client secret>
EXTERNAL_URL: http://auth.example.com
COOKIE_DOMAIN: example.com
COOKIE_SECRET: <random string of at least 32 characters>
labels:
- traefik.enable=true
- traefik.http.routers.casdoor-auth.rule=Host(`auth.example.com`)
- traefik.http.routers.casdoor-auth.entrypoints=web
- traefik.http.services.casdoor-auth.loadbalancer.server.port=9999
- traefik.http.middlewares.casdoor.forwardauth.address=http://casdoor-forward-auth:9999/auth
- traefik.http.middlewares.casdoor.forwardauth.authResponseHeaders=X-Forwarded-User,X-Forwarded-User-Id,X-Forwarded-Organization,X-Forwarded-Email,X-Forwarded-Groups,X-Forwarded-Roles

# the protected service
whoami:
image: traefik/whoami
labels:
- traefik.enable=true
- traefik.http.routers.whoami.rule=Host(`app.example.com`)
- traefik.http.routers.whoami.entrypoints=web
- traefik.http.routers.whoami.middlewares=casdoor

替换占位值:

  • CASDOOR_ENDPOINT:你的 Casdoor 服务器地址
  • CLIENT_ID 和 CLIENT_SECRET:第 1 步中得到的值
  • EXTERNAL_URL:casdoor-forward-auth 的公网地址。 <EXTERNAL_URL>/callback 必须是应用的 Redirect URL 之一
  • COOKIE_DOMAIN:受保护主机的父域名,这样会话 Cookie 会发送到所有这些主机
  • COOKIE_SECRET:一个随机密钥,例如用 openssl rand -hex 32 生成。保持不变:修改它会让所有人登出

生产环境使用 https:// 地址,这样 Cookie 只通过 HTTPS 发送。

启动服务:

docker compose up -d

要保护另一个服务,在它的路由器上加上 traefik.http.routers.<router>.middlewares=casdoor。不要给 casdoor-forward-auth 自身的路由器加这个中间件。

使用文件提供者​

如果用文件而不是 Docker 标签配置 Traefik,在动态配置中定义中间件和路由器:

http:
middlewares:
casdoor:
forwardAuth:
address: http://casdoor-forward-auth:9999/auth
authResponseHeaders:
- X-Forwarded-User
- X-Forwarded-User-Id
- X-Forwarded-Organization
- X-Forwarded-Email
- X-Forwarded-Groups
- X-Forwarded-Roles

routers:
casdoor-auth:
rule: Host(`auth.example.com`)
service: casdoor-auth
app:
rule: Host(`app.example.com`)
service: app
middlewares:
- casdoor

services:
casdoor-auth:
loadBalancer:
servers:
- url: http://casdoor-forward-auth:9999
app:
loadBalancer:
servers:
- url: http://app:8080

casdoor-forward-auth 也可以不用 Docker 运行:go install github.com/casdoor/casdoor-forward-auth@latest,然后用同样的环境变量或 JSON 配置文件启动(casdoor-forward-auth -config config.json,见 conf/config.json)。

使用单个主机​

只有一个主机名时,把 casdoor-forward-auth 挂在服务的某个路径下,例如 EXTERNAL_URL=https://app.example.com/_auth,不设置 COOKIE_DOMAIN。把这个路径路由到 casdoor-forward-auth,不加中间件:

  routers:
casdoor-auth:
rule: Host(`app.example.com`) && PathPrefix(`/_auth`)
service: casdoor-auth
app:
rule: Host(`app.example.com`)
service: app
middlewares:
- casdoor

把 forwardAuth 地址设为 http://casdoor-forward-auth:9999/_auth/auth,并在 Casdoor 的 Redirect URLs 中加上 https://app.example.com/_auth/callback。

第 3 步:测试集成​

  1. 打开受保护的服务,例如 http://app.example.com。
  2. 你会被跳转到 Casdoor 登录页。
  3. 登录后回到你打开的页面,服务会收到身份请求头。 traefik/whoami 会把它们打印出来,可以在那里检查。

身份请求头​

请求头值
X-Forwarded-User用户名,例如 alice
X-Forwarded-User-Id用户 ID
X-Forwarded-Organization用户所属的组织
X-Forwarded-Email邮箱地址
X-Forwarded-Groups逗号分隔的群组,例如 built-in/dev,built-in/ops
X-Forwarded-Roles逗号分隔的角色名

casdoor-forward-auth 总是返回所有这些请求头(可能为空),所以 Traefik 会覆盖客户端在同名请求头中发送的任何值。确保受保护的服务只能通过 Traefik 访问,否则任何人都能直接发送这些请求头。

Restricting access​

By default every user who can sign in to the Casdoor application is let in. To let in only users with certain Casdoor roles or groups, add roles and/or groups (comma separated) to the address of the middleware. Define one middleware per rule:

      - traefik.http.middlewares.casdoor-admin.forwardauth.address=http://casdoor-forward-auth:9999/auth?roles=admin,ops

A user passes with at least one of the listed roles and, if groups is given too (e.g., groups=built-in/dev), at least one of the listed groups. Everyone else who is signed in gets 403. ALLOWED_ROLES and ALLOWED_GROUPS set one rule for all services.

This also covers "sign up first, get access later": new users, e.g., from Google sign-up, have no role and get 403 until an admin assigns them one on the Roles page of Casdoor. Roles and groups are read at login, so after a change the user has to open /logout and sign in again, or wait for the session to end (SESSION_TTL).

配置​

每项设置都可以通过环境变量或 JSON 配置文件提供:

环境变量默认值映射的工具
CASDOOR_ENDPOINTrequiredCasdoor 服务器地址
CLIENT_IDrequiredCasdoor 应用的 Client ID
CLIENT_SECRET必填Casdoor 应用的 Client secret
EXTERNAL_URL必填casdoor-forward-auth 的公网地址,可以包含路径
COOKIE_SECRET必填用于签名 Cookie 的密钥,至少 32 个字符
COOKIE_DOMAIN空会话 Cookie 的域,例如 example.com
COOKIE_NAMEcasdoor_forward_auth会话 Cookie 的名称
SESSION_TTL24h会话有效期,不会超过 Casdoor 签发的访问令牌的有效期
ALLOWED_REDIRECT_DOMAINSEXTERNAL_URL 的主机和 .<COOKIE_DOMAIN>登录后允许跳回的域名,逗号分隔
ALLOWED_ROLES空Comma-separated Casdoor roles. Only users with at least one of them are let in, see Restricting access
ALLOWED_GROUPSemptyComma-separated Casdoor groups, e.g., built-in/dev. Only users in at least one of them are let in
CERTIFICATEempty用于校验访问令牌的 PEM 证书。默认从 Casdoor 的 /.well-known/jwks 获取
LISTEN_ADDR:9999监听地址

/logout 清除 casdoor-forward-auth 的会话(带 ?rd=<url> 可在之后跳转)。用户在 Casdoor 中仍保持登录。

故障排除​

Casdoor 显示 "Redirect URI ... doesn't exist in the allowed Redirect URI list"​

<EXTERNAL_URL>/callback 不在应用的 Redirect URLs 中。协议、主机、端口和路径都必须一致。

登录后又被跳回登录页​

会话 Cookie 没有发送到受保护的主机:

  • 把 COOKIE_DOMAIN 设为同时覆盖 EXTERNAL_URL 和受保护主机的域名。
  • EXTERNAL_URL 使用 https:// 时 Cookie 是 Secure 的,所以受保护的服务也必须通过 HTTPS 提供。

"the login state is missing or has expired"​

登录用时超过 10 分钟,或者浏览器拦截了 /login 设置的 Cookie。重新打开受保护的页面,开始新的登录。

前端请求返回 401​

没有会话时,只有 GET 和 HEAD 请求会被跳转到登录。其他方法的 API 调用会得到 401;让用户刷新页面登录即可。

从 traefik-casdoor-auth 升级​

casdoor-forward-auth 在 v2 之前叫 traefik-casdoor-auth,需要一个 Traefik 插件。升级方法:

  • 移除本地插件(experimental.localPlugins 和 plugins-local),改用上面所示的 forwardAuth 中间件。
  • 重命名配置项:casdoorClientId → clientId,casdoorClientSecret → clientSecret,pluginEndpoint → externalUrl。不再需要 casdoorOrganization 和 casdoorApplication,新增的 cookieSecret 是必填项。
  • Casdoor 中的 Redirect URL 仍然是 <externalUrl>/callback。

资源​