Authelia 统一身份认证实战:给反向代理前加一道 2FA 单点登录闸门


当你在 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,你就不再需要为每个应用各自维护一套零散随意的密码表,也堵上了"有服务根本没登录"的漏洞。它不会替代应用自己的鉴权,但能给所有服务罩上一层统一、可复用的第一道闸门。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

Tailscale 私有网络实战:一条命令把所有设备安全地连进一张虚拟局域网

MinIO 自托管对象存储实战:S3 兼容存储为全家桶补上最后一块拼图

评 论
更换验证码