【实用的开源工具】2FAuth:自托管的网页版两步验证器
「实用的开源工具」系列:我只收集值得用的开源项目,写清楚它能做什么、怎么部署、有哪些坑,并在文末标明出处。内容整理自官方文档,核对日期 2026-10-31,具体版本以官方为准。这篇我没有实际部署验证,命令和配置都来自官方文档和仓库;涉及我自己的判断时,会明确标出来。2FAuth 里放的是你账号的两步验证密钥,请先读完“安全与备份”一节再动手。
项目信息卡
| 项目 | 说明 |
|---|---|
| 名称 | 2FAuth |
| 做什么 | 自托管的网页应用,用来管理两步验证账号并生成验证码,手机和电脑浏览器都能用 |
| 许可证 | AGPL-3.0 |
| 仓库 | github.com/Bubka/2FAuth |
| 官方文档 | docs.2fauth.app(GitHub 仓库的 Website 字段就是这个地址) |
| 最近版本 | v9.0.0(2026-10-27 发布,查询于 2026-10-31);仓库未归档,最近一次推送 2026-10-30 |
| 运行方式 | 官方 Docker 镜像 2fauth/2fauth,容器内端口 8000,数据放在容器的 /2fauth 目录,默认用 SQLite 文件 |
| 非 Docker 部署 | 需要 PHP 8.4 以上,以及 Laravel 的服务器环境要求(见官方 README) |
| 安全提示 | 官方说明数据库加密默认关闭;APP_KEY 丢了,加密过的数据无法恢复 |
它能做什么
按官方 README 和文档:
- 管理 2FA 账号:可以用分组整理,扫描并解析二维码来添加,也可以用表单手动添加没有二维码的账号,已导入的账号还能再编辑。
- 生成验证码:支持 TOTP、HOTP 和 Steam Guard 验证码,按 RFC 4226 / RFC 6238 实现。
- 导入:官方列出的格式有 2FAuth 自己的 JSON、Google Authenticator(二维码)、Aegis(JSON、纯文本)、2FAS(JSON);v9.0.0 的发布说明里还新增了 Ente Auth 的导出格式。
- 多用户:从 4.0 版本开始支持多个用户,可以开放注册、按邮箱域名限制注册,或者完全关闭注册;也支持 SSO。
- 更安全的登录:可以用 YubiKey 之类的安全密钥(WebAuthn)登录,并关掉传统的账号密码表单。
- 自动退出登录和隐藏验证码:可以设置闲置一段时间后,或者复制验证码之后立即退出登录;验证码也可以显示成圆点。
- API 和浏览器扩展:有 REST API,浏览器扩展用个人访问令牌(PAT)认证。
它的价值很实用:在电脑前不用再掏出手机看验证码,打开浏览器就能取。代价同样明显:你把所有密钥集中放进了一台服务器,这台服务器的安全和备份,就成了你所有账号的安全和备份。
安全与备份(先读这一节)
下面这些都来自官方文档,我只在明确标注的地方加了自己的判断。
1. APP_KEY 是命根子。
- 官方说明:
APP_KEY是所有安全相关功能(会话、数据库加密、WebAuthn、个人访问令牌)用的加密密钥,要“非常安全地保管”。 - 如果启用了数据库加密,官方的原话大意是:丢了这个密钥,就没有办法再生成一次性密码,也没有补救办法。README 也强烈建议在启用加密时,备份
APP_KEY(或者整个.env文件)。 - 需要更换密钥时,把旧密钥放进
APP_PREVIOUS_KEYS(逗号分隔),避免数据无法解密、令牌失效。
2. 数据库加密要自己打开。 官方说明:数据库里的敏感数据(2FA 密钥和 otpauth URI)可以加密,用来防数据库泄露,默认是关闭的。管理员在 Admin → App setup 里勾选 Protect sensitive data 即可,对所有用户生效。
3. 谁能注册,要自己收紧。 官方文档:第一个创建的账号自动成为管理员。官方 README 说注册可以开放、按邮箱域名限制,或者完全关闭,但我没有在文档里找到“默认是哪一种”的明确说法,所以请先当作它是开放的,部署后马上去 Admin → App setup 确认并设置:
Restrict registration:只允许名单里的邮箱,或匹配某个正则的邮箱注册。官方说明限制策略不影响 SSO。Disable registration:完全关闭注册,同时也会让新用户没法通过 SSO 登录;勾选Keep SSO registration enabled可以保留 SSO 注册。
自用的话,建议你注册完自己的账号就立刻关闭注册(这是我的建议)。
4. 用 HTTPS,并让 APP_URL 和实际访问的地址一致。 官方 APP_URL 说明:通过安全连接访问时要写 https;用别的地址(比如 IP)访问时,链接生成、SSO、WebAuthn 可能不能用。官方还有 SESSION_SECURE_COOKIE 选项(默认 false),设成 true 后会话 Cookie 只会在 HTTPS 连接下发回服务器。
5. 备份里全是密钥,备份本身也要保护。
- 官方升级文档的要求是:至少备份
database.sqlite。 - 官方迁移文档里,2FAuth 导出的是 JSON 文件,格式里直接有
secret和legacy_uri字段,这意味着导出文件里是可以直接生成验证码的密钥。 - 官方迁移文档教你从 2FAS、Aegis 导出时,也要求取消勾选加密选项,导出的是未加密的迁移文件。
- 所以:导出文件和备份用完要妥善保管或删掉,不要放进网盘、聊天记录或者没有加密的公开位置。下面这几条是我的通用建议,不是项目的说法:备份文件加密后再存放;
APP_KEY不要和数据库备份放在同一个地方;定期做一次恢复演练,确认备份真的能用。
6. 别把“恢复 2FAuth 本身需要的验证码”只存在 2FAuth 里。 这是我的通用建议:2FAuth 的登录邮箱、服务器、域名注册商这类账号,要有另一种恢复方式(比如纸质恢复码),否则 2FAuth 打不开时就形成死循环。
部署
官方提供 Docker CLI 和 Docker Compose 两种写法。下面用 Compose。
先建目录。官方说明容器以 UID 1000 / GID 1000 运行,目录要修好权限:
mkdir -p /opt/2fauth && cd /opt/2fauth
mkdir 2fauth
chown 1000:1000 2fauth
chmod 700 2fauth生成 APP_KEY。官方给的做法之一,是用 2FAuth 容器自己生成:
docker run -it --rm --entrypoint /usr/bin/php 2fauth/2fauth:9.0.0 artisan key:generate --show把输出的一整行密钥记下来,稍后写进配置。官方文档示例里出现的那个 APP_KEY 只是示例,别用。
settings.env(变量名都来自官方文档和 .env.example;TRUSTED_PROXIES 和 SESSION_SECURE_COOKIE 的取值是我按反向代理场景选的):
APP_URL=https://2fauth.example.com
APP_KEY=把上一步生成的密钥整行粘贴到这里
APP_ENV=production
TRUSTED_PROXIES=*
SESSION_SECURE_COOKIE=true收紧这个文件的权限(通用建议,因为里面有 APP_KEY):
chmod 600 settings.envcompose.yaml(官方 Compose 里用的是 env_file: settings.env、./2fauth:/2fauth 和 8000 端口,我改了端口绑定,并把镜像固定到了具体版本):
services:
2fauth:
env_file: settings.env
image: 2fauth/2fauth:9.0.0
container_name: 2fauth
restart: unless-stopped
volumes:
- ./2fauth:/2fauth
ports:
- "127.0.0.1:8000:8000/tcp"启动:
docker compose up -d
docker compose logs -f 2fauth几点说明:
- 镜像标签:官方文档里,
latest是 master 分支的当前状态,可能包含还没正式发布的改动;x.y.z是和 GitHub 发布版本对应的不可变标签;也有9、9.0这样的跟随型标签;dev可能不稳定。我查 Docker Hub 时9.0.0存在(注意没有v前缀),照抄前请再确认一次。 - 数据库:官方 Docker 镜像说明里写的是“数据存在 SQLite 文件里”,已有的 SQLite 文件放在
/yourpath/2fauth/database.sqlite。所以我没有设置DB_*变量。需要 MySQL 或 PostgreSQL 的话,看官方环境变量页里DB_CONNECTION(mysql、pgsql、sqlsrv、sqlite)、DB_HOST、DB_DATABASE等说明。 - 官方还支持 Docker secrets,用
APP_KEY_FILE这样的_FILE变量(支持列表里有APP_KEY_FILE、DB_PASSWORD_FILE等);同时设置APP_KEY和APP_KEY_FILE会报错或让APP_KEY_FILE生效,不要两个都写。 - 官方有一个健康检查地址
/up,应用正常运行时返回 200,可以用来做 Docker HEALTHCHECK。 - 邮件:
.env.example里MAIL_MAILER默认是log,也就是不会真的发邮件。官方说明不配置邮件,重置密码这类功能不能用。需要的话参考官方环境变量页的MAIL_*设置。
Nginx 反向代理
官方文档没有给 Docker 版的 Nginx 反向代理示例(它给的是源码部署的 Web 服务器配置),下面是我写的通用反向代理写法,不是官方配置:
server {
listen 443 ssl;
server_name 2fauth.example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}和官方文档对得上的几点:
- 在终止 HTTPS 的代理后面,官方说可能出现生成的链接不是 https 的问题,解法是用
TRUSTED_PROXIES声明可信代理。官方的取值是以逗号分隔的代理 IP 列表,不清楚代理 IP 时可以用*信任所有代理。 - 我在上面用了
*,理由是(我的判断)端口只绑定在127.0.0.1,外网碰不到容器,能连进来的只有本机的 Nginx。你如果知道代理在容器里被看到的 IP,写具体 IP 更稳妥。 - 官方故障排查页提到:Firefox 在代理后面提示不安全连接,就是没配
TRUSTED_PROXIES。
第一次使用
- 打开
https://2fauth.example.com,注册第一个账号。按官方说明,第一个账号会自动成为管理员,页脚有 Admin 入口。 - 进入 Admin → App setup:收紧注册(见上面“安全与备份”第 3 点),按需打开
Protect sensitive data(数据库加密)。 - 添加账号:点
New,可以扫描二维码,也可以用表单手动添加。如果服务商只给你密钥字符串,用手动表单。 - 从别的验证器迁移:官方迁移文档的流程是,先在来源 App 里导出,再在 2FAuth 里点
New→Import,用Livescan扫二维码,或者Upload文件/二维码。导入分两步:先预加载并校验,列出找到的账号,重复项会被标记(仍可导入);然后选择要保存的账号,点Import或Import all,在这之前不会写入数据库。 - 导入完成后,建议逐个点开账号核对一下验证码,再去服务商那边确认能通过验证,之后再删掉旧验证器里的记录。(这一步是我的建议。)
- 个人设置:在 Settings → Options 里可以设置自动退出登录(
Auto lock)和把验证码显示成圆点;在 Settings → WebAuthn 里可以注册安全密钥。官方说明 WebAuthn 可以只当作补充,也可以作为唯一登录方式。
更新与卸载
按官方升级文档,Docker 方式是:至少先备份 database.sqlite,然后拉取新镜像并重启容器。我们固定了版本号,所以先改标签:
cd /opt/2fauth
docker compose down
cp -a 2fauth 2fauth.bak-$(date +%F) # 备份整个数据目录,里面有密钥,注意保护(见上文)
# 把 compose.yaml 里的镜像标签改成新版本(先确认 Docker Hub 上有这个标签),然后:
docker compose pull
docker compose up -d升级前请去 GitHub Releases 读发布说明。我查到的 v9.0.0 发布说明列出的是新增和修复,没有看到破坏性改动的说明;官方升级页目前单独列出的是 v8 的升级注意事项,包括:Passport 密钥文件权限(Docker 下 storage/oauth-private.key 和 oauth-public.key 要属于 1000:1000,报 chmod(): Operation not permitted 就查这个)、个人访问令牌失效需要重建,以及使用认证代理时必须配置 TRUSTED_PROXIES。如果你是从 v8 之前的版本升上来,请务必看那一页。
卸载:
cd /opt/2fauth
docker compose down这只会停掉并删除容器。数据在 ./2fauth 目录里,删掉就没有了。卸载前建议:确认所有账号都已经在别处有了新的验证方式(比如另一个验证器或恢复码),再删目录和 settings.env。如果你还有备份,也别忘了一并处理,里面同样是密钥。
踩坑清单
APP_KEY丢了,加密数据就废了。 官方文档反复强调要备份;轮换时用APP_PREVIOUS_KEYS。- 数据库加密默认是关的。 要在 Admin → App setup 里自己勾选
Protect sensitive data,而且是对所有用户生效。 - 第一个账号是管理员,注册的默认状态文档没写明。 先当它开放,注册完自己的账号就去限制或关闭注册;关闭注册也会影响 SSO,除非勾选
Keep SSO registration enabled。 - 备份和导出文件里是明文密钥。 官方的 JSON 导出格式里有
secret;从 2FAS、Aegis 导出迁移文件时官方也要求不加密。用完要妥善处理,这是整个服务里最容易泄露的地方。 APP_URL要和实际访问地址一致。 否则链接生成、SSO、WebAuthn 可能出问题;走 HTTPS 就写https。- TLS 在代理上终止,要配
TRUSTED_PROXIES。 否则可能生成非 https 的链接,Firefox 还会警告连接不安全。 - 数据目录权限。 官方要求
chown 1000:1000且chmod 700;v8 之后的 Passport 密钥文件权限不对,会遇到chmod(): Operation not permitted。 - 非 Docker 部署改了
.env之后要清配置缓存。 官方提示配置可能被缓存,需要先执行php artisan config:clear或删掉bootstrap/cache/config.php,改完再php artisan config:cache。 - 用认证代理(
AUTHENTICATION_GUARD=reverse-proxy-guard)要格外小心。 官方说明 2FAuth 只检查那个请求头是否存在,不校验内容,所以必须保证 2FAuth 只能通过你的认证代理访问;WebAuthn 和个人访问令牌在这种模式下不支持。 - 不配邮件,就没有邮件找回。 官方说明没有有效的邮件配置,重置密码等功能不可用;WebAuthn 作为唯一登录方式时,丢了设备要靠发到邮箱的恢复链接找回账号。
latest不是“最新发布版”。 官方说明latest是 master 分支的当前状态,可能包含还没正式发布的改动。生产环境固定版本号。- 版本检查会对外发请求。 官方说明开启后每周向 GitHub 请求一次最新版本号,需要走代理的话设置
PROXY_FOR_OUTGOING_REQUESTS。 - 别在公开演示站里填真实密钥。 README 里的演示站账号密码是公开的,这是通用常识:演示站只用来看界面。
- Docker 发布的端口会绕过 ufw 之类的防火墙。 这是通用的 Docker 知识,不是项目特有的问题,所以示例里绑定了
127.0.0.1。官方示例默认是对所有网卡开放的8000,如果直接暴露而不套 HTTPS,登录密码和会话都会明文传输。
出处
- 2FAuth 官方仓库 README(功能、安全机制、许可证、安装与升级入口)
- 官方文档首页
- 官方文档:Docker CLI(镜像标签、数据目录、权限)
- 官方文档:Docker Compose
- 官方文档:环境变量(APP_KEY、APP_URL、TRUSTED_PROXIES 等)
- 官方文档:升级指南
- 官方文档:升级到 v8
- 官方文档:数据保护(数据库加密、自动退出)
- 官方文档:管理(管理员、注册控制、版本检查、健康检查)
- 官方文档:迁移(导入与导出)
- 官方文档:WebAuthn
- 官方文档:认证代理
- 官方文档:故障排查
- 官方仓库的 .env.example
- Docker Hub:2fauth/2fauth(镜像标签)
- GitHub Releases
有想法或问题?去留言板留言 →