使用 Authelia 为 Caddy 反向代理提供 2FA 支持

前言

我个人的很多服务是暴露在公网的,虽然传输过程已经通过反代获得 HTTPS 加密,但是网页本身的安全性并不见得一定够高。像是 LibreSpeedHomePage 这种服务,其本身设计上就没有考虑暴露在公网的情况,但我恰恰有需要他们在公网环境下工作(公网测速、公网访问)。

最开始我的选择是使用 Basic Auth,这个所有的反代工具都会提供,需要你输入密码才能访问网站,但问题就在于它“太 Basic 了”,密码设置的太复杂不方便记,用密码管理器没办法自动填充且太麻烦;密码设置的太简单容易被爆破,最后变得形同虚设。如果能从反代层面为这些网站添加一个前置验证,使用账号密码 + 2FA的方式保护网站,那么安全性与便捷性都可以得到提升。在诸多解决方案中,我选择了 Authelia。

在 Docker Compose 中使用 Authelia

这部分官方文档写的太复杂,网上的教程能参考的又太少,最后在借助 Claude 的帮助下才搞出一个能用的配置(GPT 都抓瞎,不知道为啥现在 AI 降智这么严重),下面直接给出配置:

Caddy docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
services:
caddy:
image: ghcr.io/sixiaolong1117/my-caddy:latest
container_name: caddy
restart: always
networks:
- homelab
cap_add:
- NET_ADMIN
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./conf:/etc/caddy
- ./site:/srv
- caddy_data:/data
- caddy_config:/config
environment:
- DNS=223.5.5.5,8.8.8.8
- TZ=Asia/Shanghai

volumes:
caddy_data:
caddy_config:

networks:
homelab:
external: true

Authelia docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
services:
authelia:
image: docker.io/authelia/authelia:latest
container_name: authelia
volumes:
- ./authelia/config:/config
networks:
- homelab
ports:
- 9091:9091
restart: unless-stopped
environment:
- TZ=Asia/Shanghai
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9091/api/health"]
interval: 30s
timeout: 10s
retries: 3

redis:
image: redis:alpine
container_name: authelia-redis
volumes:
- ./authelia/redis:/data
networks:
- homelab
expose:
- 6379
restart: unless-stopped
environment:
- TZ=Asia/Shanghai

networks:
homelab:
external: true

此处我将 Caddy 与 Authelia 分在不同的 Compose 中,是因为我的 Caddy 要为很多服务提供反代,它需要独立运行以适配我的自定义配置(上述配置中未列出)。因为分开了,为了后面方便容器内网络互访,我将他们统一添加到了提前创建好的外部网络 homelab 中。对于没有相关需求的,可以直接将二者合为一体,这样就可以免去多余的外部网络配置。

Authelia 配置文件 ./authelia/config/configuration.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
---
###############################################################
# Authelia 配置文件 #
###############################################################

# 主题: light, dark, grey, auto
theme: auto

# JWT 密钥 (必须修改为随机字符串)
jwt_secret: your-super-secret-jwt-key-change-this-please

# 默认重定向 URL
default_redirection_url: https://auth.yourdomain.com

# 服务器配置
server:
host: 0.0.0.0
port: 9091
path: ""
enable_pprof: false
enable_expvars: false
disable_healthcheck: false
tls:
key: ""
certificate: ""

# 日志配置
log:
level: info
format: text
file_path: /config/authelia.log
keep_stdout: true

# TOTP 配置 (用于 Google Authenticator 等)
totp:
disable: false
issuer: yourdomain.com
algorithm: sha1
digits: 6
period: 30
skew: 1

# WebAuthn 配置 (用于 Passkey / 硬件密钥)
webauthn:
disable: false
timeout: 60s
display_name: Authelia
attestation_conveyance_preference: indirect
user_verification: preferred

# 身份验证后端 (使用文件方式)
authentication_backend:
password_reset:
disable: false
refresh_interval: 5m
file:
path: /config/users_database.yml
password:
algorithm: argon2id
iterations: 3
salt_length: 16
parallelism: 4
memory: 64

# 访问控制规则
access_control:
default_policy: deny
rules:
# 允许访问 Authelia 本身
- domain: auth.yourdomain.com
policy: bypass

# 需要双因素认证的域名
- domain: "*.yourdomain.com"
policy: two_factor

# Session 配置
session:
name: authelia_session
domain: yourdomain.com # 根域名,允许子域名共享 session
same_site: lax
secret: your-super-secret-session-key-change-this-please
expiration: 1h
inactivity: 30m
remember_me_duration: 1M

# 使用 Redis 存储 session
redis:
host: authelia-redis
port: 6379
# password: redis-password # 如果 Redis 有密码

# 存储配置 (使用本地 SQLite)
storage:
encryption_key: your-super-secret-encryption-key-change-this-please
local:
path: /config/db.sqlite3

# 通知配置 (文件系统方式,生产环境建议使用 SMTP)
notifier:
disable_startup_check: false
filesystem:
filename: /config/notification.txt

# SMTP 配置示例 (推荐生产环境使用)
# notifier:
# smtp:
# host: smtp.gmail.com
# port: 587
# timeout: 5s
# username: [email protected]
# password: your-app-password
# sender: "Authelia <[email protected]>"
# identifier: localhost
# subject: "[Authelia] {title}"
# startup_check_address: [email protected]
# disable_require_tls: false
# disable_html_emails: false
# tls:
# skip_verify: false
# minimum_version: TLS1.2

# 监管配置
regulation:
max_retries: 3
find_time: 2m
ban_time: 5m
  • 配置中的 jwt_secretsession.secretstorage.encryption_key 可以通过命令:
    1
    openssl rand -hex 32
    生成随机哈希填进去。
  • 配置中的所有 yourdomain.com 都要替换为实际域名。

Authelia redis 用户数据库文件 ./authelia/config/users_database.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
###############################################################
# 用户数据库 #
###############################################################

users:
# 用户名: admin
admin:
displayname: "管理员"
默认密码: password123 (容器内生成密码哈希,见下文)
email: [email protected]
groups:
- admins
- dev

# 用户名: user
user:
displayname: "普通用户"
默认密码: password123 (容器内生成密码哈希,见下文)
email: [email protected]
groups:
- users

密码可以通过容器内工具生成(注意修改 your-password-here):

1
2
# 生成密码哈希
docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'your-password-here'

Caddy 配置

我们需要首先为 Authelia 认证门户本身进行反代:

1
2
3
auth.yourdomain.com {
reverse_proxy authelia:9091
}

此处可以使用 authelia:9091 的原因就在于 Caddy 与 Authelia 在同一个网络中。

其他需要借助 Authelia 门户认证的网页,可以设置一个 forward_auth

1
2
3
4
5
6
7
8
homepage.yourdomain.com {
forward_auth authelia:9091 {
uri /api/verify?rd=https://auth.yourdomain.com
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}

reverse_proxy <IP>:<Port>
}

如果你使用非标准端口(如 44443),则应该转到:

1
/api/verify?rd=https://auth.yourdomain.com:44443

Authelia 认证门户设置

通过访问 auth.yourdomain.com 进入 Authelia 的 Web 页面,东西很少,一共就支持俩东西:TOTP 和 WebAuth,没啥可讲的。

这里主要说一下 Authelia 在设计上是需要通过发送邮箱验证码来确认用户身份的,你添加 TOTP 和 WebAuth 都会提示从邮箱接收验证码。

但是如果你只是个人使用,不想配置 SMTP(前文注释部分),而是使用了 filesystem(前文配置),Authelia 前端依旧会告诉你发送了右键,不过你可以通过 ./authelia/config/notification.txt 来获取验证码内容通过验证。

有关 Authelia 配置的补充

一些应用需要通过 APP 使用,这些 APP 的主流实现方式就是填入 URL 与账号密码,直接登陆。设置了 Authelia forward_auth 之后这些 APP 的使用会出现异常,所以我们需要在 access_control 部分添加相应的 Bypass。例如对于 Gotify 来说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 访问控制规则
access_control:
default_policy: deny
rules:
# 允许访问 Authelia 本身
- domain: auth.yourdomain.com
policy: bypass

# Gotify
- domain: "gotify.yourdomain.com"
policy: bypass # 此规则下的请求绕过 Authelia 认证
resources: # 只允许以下 API 路径绕过认证
- '^/message([/?].*)?$'
- '^/version([/?].*)?$'
- '^/stream([/?].*)?$'
- '^/current([/?].*)?$'
- '^/client([/?].*)?$'
- '^/application([/?].*)?$'
- '^/user([/?].*)?$'
- '^/health([/?].*)?$'
- '^/plugin([/?].*)?$'
# 如果应用图标不显示,可以尝试额外添加以下规则
- '^/image([/?].*)?$' # 自己上传的 LOGO
- '^/static/.*$' # 默认 LOGO

# 需要双因素认证的域名
- domain: "*.yourdomain.com"
policy: two_factor

对于不同的网站来说,需要设置 Bypass 的规则也不同,有需要的建议在 AI 的帮助下结合个人实践酌情添加。不建议为有重度 APP 需求,且页面过于复杂的网站添加 Authelia 2FA。

结语

Authelia 本身的配置以及与 Caddy 结合使用都不算复杂,但是因为文档太繁琐,教程太少,中间还是踩了不少坑。

Authelia 支持 WebAuth,我一共存储了两个密钥,一个在 Bitwarden 中用于从我自己的电脑快速登陆,一个在之前文章中提到的 Pico Key 中,可以在陌生设备上快速登陆,实现了便捷性与安全性双赢。