Authelia 自建认证网关实战:用 forward auth 给所有自托管服务加统一登录与 2FA,应用零改造

自建服务越搭越多,认证这件事迟早要统一

刚开始玩自托管的时候,你大概只跑了一个博客。后来越搭越多:一个用来访问家里文件的文件管理面板、一个监控面板、一个下载器、一个数据库管理工具、一个内网的 Git 服务……每个服务自己都带一套登录,于是你要记七八组账号密码。更麻烦的是安全:有的服务密码设得随意,有的干脆默认口令没改,有的暴露在公网只靠一个弱密码挡着。你可能还开了一个 Nginx basic auth 给几个内网服务加了层保护,但 basic auth 的账号密码在浏览器里到处弹框,体验很差,而且改密码要一个个 Nginx 配置去改。

这就是"认证碎片化"。真正优雅的做法是:所有自建服务共用一套登录体系,登录一次就能访问全部,后台统一管理用户和第二因素(2FA),还不用改被保护服务本身的代码。在自托管圈子里,干这件事最出名的工具是 Authelia。

先说清楚它和同类方案的界限。前面站里提过 OAuth2 + JWT 那套,那是"程序自己怎么实现鉴权"的层面,是给开发者写代码用的。而 Authelia 是一个独立运行的认证网关,它不改造你的应用,而是站在应用前面,靠 Nginx 的 forward auth 机制拦截请求、替应用完成"这个人登录了没、有没有权限"的判断。这就是它最大的优点——你的应用完全不用动,哪怕它本身根本不支持 SSO。

Authelia 的工作原理:forward auth 是关键

要把原理讲清楚,得先理解 forward auth 这个 Nginx 机制。当用户请求一个被保护的服务时,不是在应用那里做判断,而是 Nginx 先把这次请求"转发"给 Authelia 的一个校验接口(/api/verify)。Authelia 看一眼请求里带的会话 Cookie:

  • 会话有效且用户有权限 → 返回 200,Nginx 放行,请求转发给应用。
  • 会话无效或没登录 → 返回 401,Nginx 把用户重定向到 Authelia 的登录页。
  • 用户权限不够(比如不是管理员组)→ 返回 403,拒绝访问。

用户登录成功后,Authelia 给他种一个 Cookie(会话),之后所有受保护服务都认这个 Cookie,所以只登录一次就能横穿全部服务。整个过程应用本身毫无感知,它甚至不知道 Authelia 的存在。

这就带来了几个实实在在的好处:统一登录体验、统一开关 2FA、统一审计谁在什么时候访问了什么、统一在后台加删用户、以及——最重要的一点——把认证这个安全要害从七八个应用里收拢到一个地方维护。

用 Docker 部署 Authelia

Authelia 官方镜像很小,部署不复杂。先建目录结构:

mkdir -p /opt/authelia/{config,secrets}
cd /opt/authelia

生成几个必须的密钥。Authelia 会自己做身份签名和会话加密,这两组密钥必须是你自己生成的随机值,绝不能从教程里照抄:

# 生成三个随机密钥
openssl rand -hex 32 > secrets/jwt_secret
openssl rand -hex 32 > secrets/session_secret
openssl rand -hex 32 > secrets/storage_encryption_key

# 生成用户密码的 argon2 哈希(用于 users_database.yml)
docker run --rm authelia/authelia:latest \
  authelia crypto hash generate argon2 --password '你的登录密码'

然后写主配置文件 config/configuration.yml:

theme: dark

default_redirection_url: https://auth.example.com

server:
  host: 0.0.0.0
  port: 9091

log:
  level: info

totp:
  issuer: auth.example.com

authentication_backend:
  file:
    path: /config/users_database.yml

access_control:
  default_policy: deny
  rules:
    # 管理员组才能访问的部分
    - domain: "admin.example.com"
      policy: two_factor
      subject: "group:admins"
    # 普通用户可访问
    - domain: "panel.example.com"
      policy: two_factor
      subject: "group:users"

session:
  name: authelia_session
  secret: "@@JWT_SESSION@@"
  expiration: 1h
  inactivity: 10m
  domain: example.com

regulation:
  max_retries: 5
  find_time: 2m
  ban_time: 5m

storage:
  local:
    path: /config/db.sqlite3

notifier:
  smtp:
    host: smtp.example.com
    port: 587
    username: no-reply@example.com
    password: "@@SMTP_PASS@@"
    sender: "Authelia <no-reply@example.com>"

几个关键字段解释一下。default_policy: deny 表示"默认拒绝一切",这是一个非常重要的安全默认——你只显式放行要保护的服务,漏配的服务不会意外敞开。access_control 里的规则按顺序匹配,two_factor 表示该服务要求通过密码 + 第二因素。regulation 是防暴力破解的限速,max_retries: 5 配合 ban_time 表示连续失败就临时封禁。

然后是用户库 config/users_database.yml,把所有自建服务的账号放进这一个文件:

users:
  admin:
    displayname: "站长"
    password: "$argon2id$v=19$m=65536,t=3,p=4$这里填上面生成的哈希"
    email: admin@example.com
    groups:
      - admins
      - users

  guest:
    displayname: "访客"
    password: "$argon2id$v=19$m=65536,t=3,p=4$另一个哈希"
    email: guest@example.com
    groups:
      - users

注意密码字段要填 authelia crypto hash generate argon2 输出的完整哈希串(从 $argon2id$ 开头),别填明文。

compose 文件,同样把端口绑在回环地址,对外只让 Nginx 访问:

version: "3.8"
services:
  authelia:
    image: authelia/authelia:latest
    restart: unless-stopped
    volumes:
      - ./config:/config
    environment:
      - AUTHELIA_JWT_SECRET_FILE=/config/secrets/jwt_secret
      - AUTHELIA_SESSION_SECRET_FILE=/config/secrets/session_secret
      - AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/config/secrets/storage_encryption_key
    ports:
      - "127.0.0.1:9091:9091"

把 secrets/ 目录里的三个密钥文件挂进容器,通过环境变量指向它们。启动:

docker compose up -d
docker compose logs -f authelia

日志里应该能看到 Authelia 正常启动、加载了用户库。第一次访问 https://auth.example.com 会要求你绑定一个 TOTP 应用(比如 Google Authenticator),扫二维码完成绑定,这就是你的第二因素。

在 Nginx 里配置 forward auth

Authelia 自身不接管流量,真正的"拦截"由 Nginx 完成。假设你要保护 panel.example.com,Nginx 配置长这样:

server {
    listen 443 ssl http2;
    server_name panel.example.com;

    location / {
        auth_request /authelia/api/verify;
        auth_request_set $user $upstream_http_remote_user;

        proxy_set_header Remote-User $user;
        proxy_pass http://127.0.0.1:8080;
    }

    # forward auth 的内部校验入口
    location = /authelia/api/verify {
        internal;
        proxy_pass http://127.0.0.1:9091/api/verify;
        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-Original-Method $request_method;
    }

    # 未登录时跳转到 Authelia 登录页
    error_page 401 =302 https://auth.example.com/?rd=$scheme://$http_host$request_uri;
    error_page 403 =302 https://auth.example.com/?rd=$scheme://$http_host$request_uri;
}

这段配置的核心是三件事。auth_request /authelia/api/verify 让 Nginx 在放行前先去问 Authelia;internal 让这个校验入口只能内部调用,外部无法直接访问;error_page 401 把未登录状态重定向到登录页并带上原始 URL(rd 参数),这样用户登录成功后能回到原来想去的页面,体验才顺。

proxy_set_header Remote-User $user 这一行很有用:它把 Authelia 校验出来的用户名透传给后端应用,一些支持"信任上游认证"的应用(比如部分面板)可以直接读取这个头,知道当前是谁在访问,不用自己再登录一遍。

被保护的服务有多少个,就复制多少份这样的配置块,把 server_name、proxy_pass 换成对应服务即可。因为域名同属 example.com,Authelia 的会话 Cookie 在子域间共享,用户登录一次就能畅通所有子域。

给不同人不同权限:分组和细粒度规则

真正好用的地方在于权限粒度。你可以在 access_control 里按域名、按路径、按用户组、按用户做规则,比如让"访客只能看监控只读面板,管理员才能进管理后台":

access_control:
  default_policy: deny
  rules:
    - domain: "panel.example.com"
      resources:
        - "^/admin.*$"
      policy: two_factor
      subject: "group:admins"

    - domain: "panel.example.com"
      policy: one_factor
      subject: "group:users"

resources 用正则匹配路径,subject 指定用户组。one_factor 表示只验密码不要求 2FA(适合低敏感度面板),two_factor 则要求密码 + TOTP。这样管理员进敏感区域享受最高防护,普通用户又能用更轻的方式访问,权责分明。

规则还可以只对特定网络生效,比如"内网完全免登录,公网才要求认证":

    - domain: "panel.example.com"
      networks:
        - 192.168.1.0/24
      policy: bypass

这在家庭自托管场景里特别好用:在家里 Wi-Fi 下直接访问,出门走公网就必须登录加 2FA,安全性和便利性都拿到了。

加第二因素:TOTP 和 WebAuthn

Authelia 支持两种主流的第二因素。TOTP 就是你用 Google Authenticator / 1Password 扫个二维码,之后每次登录输入 6 位动态码,配置最简单,兼容性最好。WebAuthn 是用硬件密钥(YubiKey)或手机指纹/面容登录,安全性更高、体验也更好——插入密钥或验证指纹即完成登录,不用手输验证码。对于最敏感的管理后台,强烈建议用 WebAuthn;普通服务用 TOTP 就够。

开启 2FA 只需要在 access_control 里把对应规则写成 policy: two_factor,用户首次登录时 Authelia 会自动引导他完成绑定。用户也可以在 Authelia 的个人设置页里自助管理自己的 TOTP 设备、重新生成恢复码。

五个部署时必踩的坑

第一,密钥照抄教程里的默认值。这是最致命的。jwt_secret、session_secret、storage_encryption_key 必须是你自己用 openssl 生成的随机值。用公开教程里的默认值等于把会话伪造的钥匙交给了全世界——攻击者可以用默认密钥自己签一个合法会话,绕过所有认证。

第二,访问规则配得太宽。Authelia 里最常见的配置错误是把 default_policy 写成 one_factor 就以为万事大吉,结果规则匹配写错、漏掉某个服务,导致它默认可被弱认证访问。安全的默认永远是 deny,再一条条放行。

第三,忘了配 notifier。没有 SMTP notifier 或 notifier 配错,用户密码重置、TOTP 绑定确认这些流程会静默失败,你根本收不到重置邮件。上线前一定要测通一封邮件。

第四,storage 用默认的 SQLite 却跑多实例。个人站单机 SQLite 完全够用,但如果你为了"高可用"起了多个 Authelia 实例共享一个 SQLite 文件,会损坏数据库。要么坚持单机,要么换成 PostgreSQL 后端。

第五,9091 端口暴露公网。Authelia 的校验接口绝不能被外部直接访问,必须只让 Nginx 内部调用(配置里的 internal 就是干这个的)。9091 绑在回环地址,公网只暴露 auth.example.com 这个走 HTTPS 的域名。

值得吗?个人站的一笔划算买卖

有人会说:我就一个人用,七八组密码记记也就记了,上 Authelia 是不是杀鸡用牛刀?我的看法恰恰相反。恰恰因为是一个人维护,你才更需要把认证收拢到一个地方——否则每个新加的自建服务,你都要重新想一遍"它该怎么鉴权、密码设什么、要不要加 basic auth",时间一长疏漏必然出现,而一旦某个暴露在公网的服务被拿下,可能就是整台机器的沦陷。

Authelia 把这件事变成了一次性投入、长期受益:一次配置,之后所有自建服务都自动获得统一登录、统一 2FA、统一审计、统一封禁策略。这份省心和安全感,对于一个既当开发又当运维又当安全负责人的个人站长来说,性价比高得离谱。如果你手上已经有三四个自托管服务还在各用各的登录,那今天就可以开始把它们统一到 Authelia 门后了。

Last modification:October 12th, 2026 at 08:26 pm

Leave a Comment