当你在 Traefik 后面托管了七八个自建应用,每个应用可能有自己的登录页、有的干脆没有登录——于是你的服务就像一串各自上锁的房门,有的锁还开着门。统一身份认证(SSO)解决的是"把这一串门的钥匙统一成一把、而且都上锁"这件事。Authelia 正是为这个场景设计的开源方案:它站在反向代理前面,为一个域下的所有子服务统一做登录与 2FA 校验,未登录的用户先被拦在 Authelia 的登录页,只有通过验证才放行到目标应用。这篇讲清它的架构、部署、用户与访问控制,以及和反向代理配合的正确姿势。
一、架构理解:Authelia 是"门卫",反代是"闸机"
Authelia 不做反向代理本身,它做的是"认证授权"这一件事。典型的拓扑里,Traefik/Nginx/Caddy 作为唯一入口接收外部流量,按域名把请求路由给对应后端服务;Authelia 作为这些路由的"守卫中间件"挂在反代上。请求到达某个受保护服务时,反代先把请求交给 Authelia 中间件判定"这个客户端有没有通过认证",没有就 302 重定向到 Authelia 登录页,通过后反代才把流量转发到真正的应用。
关键区分在于认证状态由谁管理:Authelia 用自己的 cookie(名为 authelia_session)维护登录会话,但它是通过反代透传的 header(如本域下发的 session 信息)来判断的。这意味着"哪条路由受保护、什么条件下放行",是用那套路径/角色/条件规则(rules)描述的,而非写死在应用里。你的应用服务本身甚至可以没有登录——只要把它藏在 Authelia 后面,访问权就统一由 Authelia 说了算。这比一个个应用各自维护密码表,安全边界干净得多。
二、部署:配置文件把逻辑讲清楚
Authelia 的配置集中在几个 YAML 里,核心是身份认证(Authentication)、会话(Session)、存储(Storage)三块。一个自托管的 configuration.yml 骨架大致如下,需要和接入的反代地址、后端存储(可选 SQLite)对上:
# configuration.yml
host: 0.0.0.0
port: 9091
log_level: warn
jwt_secret: 一个足够长的随机串
identity_validation:
reset_password: disable
authentication_backend:
file:
path: /config/users.yml
session:
name: authelia_session
secret: 一个强的会话密钥
expiration: 1h
inactivity: 15m
domain: example.com # 覆盖你所有子服务的根域
storage:
local:
path: /config/db.sqlite3
access_control:
default_policy: deny # 默认拒绝,只有显式放行才通过
rules:
- domain: "auth.example.com"
policy: bypass
- domain: "*.example.com"
policy: two_factor
subject: "group:admins"
一个反复出现的部署要点是 session.domain:它必须设成你所有受保护子服务的公共根域(如 example.com),并且这些服务都要在同一个根域下(如 traefik.example.com、photos.example.com),这样才能让跨子域共享那个登录 cookie。如果每个服务各自独立域名,cookie 的作用域就断裂了,SSO 也就失去意义。另一个逃不掉的坑是 HTTPS——Authelia 的 cookie 需要 secure 且仅在 TLS 下工作,所以它必须放在反代后面启用 HTTPS,别把它裸开在 http 上。
三、用户与 2FA:密码、TOTP 与 WebAuthn
用户存在 users.yml 中,每个用户可以有明文或 bcrypt 哈希的密码,以及开启 2FA 需要的 TOTP 秘密或 WebAuthn 密钥。特别注意:不要把密码明文写在配置里进 git。用官方提供的命令为密码生成 bcrypt 哈希,比直接抄一串明文安全得多:
# 用 docker 生成 bcrypt 哈希做密码字段
docker run authelia/authelia:latest authelia crypto hash generate argon2 --password '你的强密码'
# users.yml 示例
users:
admin:
displayname: "Admin"
password: "$argon2id$v=19$m=65536,t=3,p=4$...."
email: admin@example.com
groups:
- admins
sam:
displayname: "Sam"
password: "$argon2id$v=19$m=65536,t=3,p=4$...."
groups:
- users
开启 2FA 后,登录时除了密码还要验证 TOTP 或 WebAuthn,这是"一个人拿到你密码也进不来"的护栏。首次登录通常要求先注册/绑定 2FA 设备。这一步把"仅密码"的个人自托管,提升到"密码 + 动态验证"的企业级安全基线——对暴露在公网的服务尤其值得,因为它直接把撞库和密码猜测挡在门外。
四、访问控制规则:默认拒绝,再逐条放行
Authelia 的访问控制是最需要花心思的部分,也是出安全问题的高发区。它的基础哲学是默认拒绝(default deny),再对需要放行的路径/域名逐条写规则。一个常见的误配置是图省事把 default_policy 设成 allow 或 bypass——那等于把统一认证这层直接关掉,回到"每个应用自己裸奔"。正确姿势是保持 deny,把必须公开的资源(比如健康检查、静态 CSS)单独用更宽松的策略放行。
access_control:
default_policy: deny
rules:
- domain: "traefik.example.com"
policy: one_factor # 只输密码
- domain: "photos.example.com"
policy: two_factor # 必须 2FA
subject: "group:admins"
- domain: "health.example.com"
resources: ["/healthz"]
policy: bypass # 仅健康检查放行
规则是"从上往下,命中即停"的匹配逻辑:越具体的放前面,越宽泛的兜底放后面。区分 one_factor(仅密码)与 two_factor(必须 2FA)很重要——给管理类、敏感类服务用 two_factor,给一般只读服务用 one_factor,既保证关键入口的安全,又不至于让每个子服务都变得繁琐。写规则时按"哪些路径我确定要公开、其余全部要认证"的思路逐条收敛,能避免最常见的"误放行"。
五、与反代接合:一个中间件摘要,接入所有服务
接入 Authelia 的工作量集中在"把它设为反代的一个可复用中间件"。以 Traefik 为例,用 Docker label 或动态配置,把负责处理前向认证(forward-auth)的中间件挂到受保护路由上;Nginx/Caddy/HAProxy 同理,各写一段转发到 Authelia 的认证请求逻辑。关键不在于每家语法不同,而在于共同点:受保护路由要先跳到 /api/authz/forward-auth,Authelia 校验会话后允许或拒绝。
# Traefik docker label 里为受保护服务挂上 forward-auth 中间件
- "traefik.http.routers.photos.middlewares=auth@file"
- "traefik.http.middlewares.auth.forwardAuth.address=http://authelia:9091/api/authz/forward-auth"
- "traefik.http.middlewares.auth.forwardAuth.trustForwardHeader=true"
- "traefik.http.middlewares.auth.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email"
通过后,反代把 Remote-User、Remote-Groups 等 header 传给后端应用——后端应用于是知道"当前是谁、属于哪个组",实现单点登录 + 基于角色的授权(比如只有管理员能进管理后台)。把认证逻辑统一在这一层,意味着后续新增任何受保护服务,只需给它的路由挂上同一个中间件,不必在应用内部再各写一份登录逻辑。
结语
Authelia 把"一域下所有自托管的门统一成一把钥匙、且都上了 2FA 锁"这件事从"想法"变成了可落地的一层基础设施。它的成功取决于三件事是否到位:会话 domain 跨子域一致、默认拒绝 + 逐条收敛的访问规则、以及和反代的前向认证接合。当你把管理类、照片类等服务设为 two_factor、把一般只读服务放在 one_factor、并把其余默认 deny,你就不再需要为每个应用各自维护一套零散随意的密码表,也堵上了"有服务根本没登录"的漏洞。它不会替代应用自己的鉴权,但能给所有服务罩上一层统一、可复用的第一道闸门。



