用 Docker 搭建鉴权体系的完整流程
家里服务器上跑的自托管服务越来越多,Gitea、Vaultwarden、qBittorrent,之前每个都是各管各的账号密码,有两个甚至直接裸奔在公网上,每次想起这事心里都有点发毛。本来想图省事,在每个应用前面套一层 Nginx Basic Auth,用了一阵发现体验稀碎,qb 的本地客户端压根不认这套东西。后来研究了一圈,决定上 Authelia 配合 Nginx 做统一鉴权,全部用 Docker 跑。Authelia 的文档其实挺全,就是散得到处都是,一个配置项能让你在官网、GitHub 示例和 FAQ 之间来回跳,所以干脆把整个过程记成一篇,下次重装直接照着抄。
为什么选 Authelia 而不是 Keycloak
一开始其实想用 Keycloak,毕竟名气大,教程也多。结果拉下来一看,这玩意儿自己跑不起来,还得再拖一个 PostgreSQL,就为了给几个个人服务加个登录,养一个数据库属实有点重了。Authelia 是单个容器,配置全是 yaml,存储直接用 sqlite 就够了,对个人自托管这个量级来说刚刚好,那就它了。
目录结构先定下来
/opt/auth/
├── docker-compose.yml
├── nginx/
│ ├── conf.d/default.conf
│ └── certs/ # 证书怎么签随意,wildcard 一张就够
└── authelia/
├── configuration.yml
├── users.yml
├── db.sqlite3 # 跑起来之后自动生成
└── secrets/
├── jwt_secret
└── storage_encryption_key
用 docker-compose 把服务拉起来
Authelia 官方镜像在 Docker Hub 上,国内拉取经常超时,我这里配了镜像加速域名,拉的时候报错的话可以把 docker.1ms.run/ 前缀删掉,没准儿你用的时候这个加速域名已经不可用了。
services:
authelia:
image: docker.1ms.run/authelia/authelia:4.38
container_name: authelia
restart: unless-stopped
volumes:
- ./authelia:/config
networks:
- auth
nginx:
image: docker.1ms.run/library/nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/certs:/etc/nginx/certs
networks:
- auth
networks:
auth:
external: true
注意这个 auth 网络是先手动 docker network create auth 建出来的,然后在所有 compose 文件里声明成 external。这样写的好处是业务容器不管在哪个 compose 文件里,只要挂上这个网络,Nginx 就能通过容器名直接反代到它。Authelia 不需要映射任何端口到宿主机,反正只有 Nginx 找它。
Authelia 的最小配置
configuration.yml,我把用不上的全删了,只留能跑通的部分(把 example.com 换成你自己的域名,下同):
server:
port: 9091
authentication_backend:
file:
path: /config/users.yml
storage:
encryption_key: {{ secret "/config/secrets/storage_encryption_key" }}
local:
path: /config/db.sqlite3
session:
secret: {{ secret "/config/secrets/jwt_secret" }}
cookies:
- domain: example.com # 注意是父域,写错了 cookie 不会跨子域
authelia_url: https://auth.example.com
access_control:
default_policy: deny
rules:
- domain: qb.example.com
policy: two_factor
- domain: git.example.com
policy: one_factor
regulation:
max_retries: 3
find_time: 2m
ban_time: 5m
两个密钥文件用 openssl 随便生成一下就行:
openssl rand -hex 32 > authelia/secrets/jwt_secret
openssl rand -hex 32 > authelia/secrets/storage_encryption_key
users.yml 里放用户和密码哈希,格式很简单:
users:
me:
displayname: Me
password: $argon2id$v=19$m=65536,t=3,p=4$...
这个哈希也用 docker 生成,不然你就得在宿主机上折腾 argon2 的工具链,没必要:
docker run --rm docker.1ms.run/authelia/authelia:4.38 authelia crypto hash generate argon2 --password '你的密码'
由于加了 --rm,哈希生成完容器就自动删了。注意密码参数一定要用单引号包起来,不然输出里 $argon2id$ 开头那串 $ 会被 shell 当变量吃掉,生成的哈希直接作废,之后登录永远提示账号密码错误,别问我怎么知道的。
Nginx 侧接入 forward auth
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# 登录门户本身,这个域名千万不能套鉴权
server {
listen 443 ssl;
server_name auth.example.com;
ssl_certificate /etc/nginx/certs/example.com.crt;
ssl_certificate_key /etc/nginx/certs/example.com.key;
location / {
proxy_pass http://authelia:9091;
proxy_set_header X-Original-URL $scheme://$host$request_uri;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# 被保护的业务,以 qb 为例
server {
listen 443 ssl;
server_name qb.example.com;
ssl_certificate /etc/nginx/certs/example.com.crt;
ssl_certificate_key /etc/nginx/certs/example.com.key;
location /internal/authelia/authverify {
internal;
proxy_pass http://authelia:9091/api/verify;
proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Original-URL $scheme://$host$request_uri;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Content-Length "";
proxy_set_header Connection "";
}
location / {
auth_request /internal/authelia/authverify;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $redirect $upstream_http_location;
error_page 401 =302 $redirect;
proxy_set_header Remote-User $user;
# 下面三行是给 websocket 用的,qb 和 Gitea 都需要
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://qbit:8080;
}
}
另外再加一个 80 端口的 server 块做 301 跳转,这里就不贴了。然后 docker compose up -d,接着 docker compose logs -f authelia 看到 Startup complete 就算起来了。不出问题的话,打开 https://qb.example.com 会先被踢到登录页,第一次登录会让你绑 TOTP,随便找个验证器 App 扫一下就行。
踩过的坑
第一个坑是无限重定向。我一开始图省事把 auth.example.com 也挂上了 auth_request,登录页自己去校验自己,浏览器一路 302 到天荒地老。鉴权门户必须裸奔,切记。
第二个坑还是 cookie。session 里的 domain 写成了 auth.example.com,结果 cookie 不会带到别的子域,每访问一个服务都被踢回登录页,看起来像登录失效,其实就是域写小了。
第三个坑是 502。新反代一个容器,页面直接 502,查了半天发现是新容器忘了挂 auth 网络,Nginx 解析不到容器名。
最后一个坑是客户端类应用。浏览器里一切都好,但 qb 的本地客户端和一些命令行工具不走 cookie,会被 forward auth 直接挡掉。这类场景要么在 access_control 里给对应路径加 bypass 规则、靠应用自己的 API key 兜底,要么就老老实实浏览器里用。
之后加新服务怎么办
基本就是三步:DNS 加一条子域解析、Nginx 复制一个 server 块改两处域名、configuration.yml 里加一条规则,然后 docker compose exec nginx nginx -s reload。改完发现 502,八成是新容器忘了挂网络;发现无限跳登录,八成是 cookie 的 domain 写错了。整套跑下来其实没几行配置,但初次搭的时候文档翻得人头晕,希望能省下你一个晚上。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。