【实用的开源工具】2FAuth:自托管的网页版两步验证器

阅读约 13 分钟
【实用的开源工具】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 运行,目录要修好权限:

bash
mkdir -p /opt/2fauth && cd /opt/2fauth
mkdir 2fauth
chown 1000:1000 2fauth
chmod 700 2fauth

生成 APP_KEY。官方给的做法之一,是用 2FAuth 容器自己生成:

bash
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 的取值是我按反向代理场景选的):

env
APP_URL=https://2fauth.example.com
APP_KEY=把上一步生成的密钥整行粘贴到这里
APP_ENV=production
TRUSTED_PROXIES=*
SESSION_SECURE_COOKIE=true

收紧这个文件的权限(通用建议,因为里面有 APP_KEY):

bash
chmod 600 settings.env

compose.yaml(官方 Compose 里用的是 env_file: settings.env、./2fauth:/2fauth 和 8000 端口,我改了端口绑定,并把镜像固定到了具体版本):

yaml
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"

启动:

bash
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 服务器配置),下面是我写的通用反向代理写法,不是官方配置:

nginx
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。

第一次使用

  1. 打开 https://2fauth.example.com,注册第一个账号。按官方说明,第一个账号会自动成为管理员,页脚有 Admin 入口。
  2. 进入 Admin → App setup:收紧注册(见上面“安全与备份”第 3 点),按需打开 Protect sensitive data(数据库加密)。
  3. 添加账号:点 New,可以扫描二维码,也可以用表单手动添加。如果服务商只给你密钥字符串,用手动表单。
  4. 从别的验证器迁移:官方迁移文档的流程是,先在来源 App 里导出,再在 2FAuth 里点 New → Import,用 Livescan 扫二维码,或者 Upload 文件/二维码。导入分两步:先预加载并校验,列出找到的账号,重复项会被标记(仍可导入);然后选择要保存的账号,点 Import 或 Import all,在这之前不会写入数据库。
  5. 导入完成后,建议逐个点开账号核对一下验证码,再去服务商那边确认能通过验证,之后再删掉旧验证器里的记录。(这一步是我的建议。)
  6. 个人设置:在 Settings → Options 里可以设置自动退出登录(Auto lock)和把验证码显示成圆点;在 Settings → WebAuthn 里可以注册安全密钥。官方说明 WebAuthn 可以只当作补充,也可以作为唯一登录方式。

更新与卸载

按官方升级文档,Docker 方式是:至少先备份 database.sqlite,然后拉取新镜像并重启容器。我们固定了版本号,所以先改标签:

bash
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 之前的版本升上来,请务必看那一页。

卸载:

bash
cd /opt/2fauth
docker compose down

这只会停掉并删除容器。数据在 ./2fauth 目录里,删掉就没有了。卸载前建议:确认所有账号都已经在别处有了新的验证方式(比如另一个验证器或恢复码),再删目录和 settings.env。如果你还有备份,也别忘了一并处理,里面同样是密钥。

踩坑清单

  1. APP_KEY 丢了,加密数据就废了。 官方文档反复强调要备份;轮换时用 APP_PREVIOUS_KEYS。
  2. 数据库加密默认是关的。 要在 Admin → App setup 里自己勾选 Protect sensitive data,而且是对所有用户生效。
  3. 第一个账号是管理员,注册的默认状态文档没写明。 先当它开放,注册完自己的账号就去限制或关闭注册;关闭注册也会影响 SSO,除非勾选 Keep SSO registration enabled。
  4. 备份和导出文件里是明文密钥。 官方的 JSON 导出格式里有 secret;从 2FAS、Aegis 导出迁移文件时官方也要求不加密。用完要妥善处理,这是整个服务里最容易泄露的地方。
  5. APP_URL 要和实际访问地址一致。 否则链接生成、SSO、WebAuthn 可能出问题;走 HTTPS 就写 https。
  6. TLS 在代理上终止,要配 TRUSTED_PROXIES。 否则可能生成非 https 的链接,Firefox 还会警告连接不安全。
  7. 数据目录权限。 官方要求 chown 1000:1000 且 chmod 700;v8 之后的 Passport 密钥文件权限不对,会遇到 chmod(): Operation not permitted。
  8. 非 Docker 部署改了 .env 之后要清配置缓存。 官方提示配置可能被缓存,需要先执行 php artisan config:clear 或删掉 bootstrap/cache/config.php,改完再 php artisan config:cache。
  9. 用认证代理(AUTHENTICATION_GUARD=reverse-proxy-guard)要格外小心。 官方说明 2FAuth 只检查那个请求头是否存在,不校验内容,所以必须保证 2FAuth 只能通过你的认证代理访问;WebAuthn 和个人访问令牌在这种模式下不支持。
  10. 不配邮件,就没有邮件找回。 官方说明没有有效的邮件配置,重置密码等功能不可用;WebAuthn 作为唯一登录方式时,丢了设备要靠发到邮箱的恢复链接找回账号。
  11. latest 不是“最新发布版”。 官方说明 latest 是 master 分支的当前状态,可能包含还没正式发布的改动。生产环境固定版本号。
  12. 版本检查会对外发请求。 官方说明开启后每周向 GitHub 请求一次最新版本号,需要走代理的话设置 PROXY_FOR_OUTGOING_REQUESTS。
  13. 别在公开演示站里填真实密钥。 README 里的演示站账号密码是公开的,这是通用常识:演示站只用来看界面。
  14. Docker 发布的端口会绕过 ufw 之类的防火墙。 这是通用的 Docker 知识,不是项目特有的问题,所以示例里绑定了 127.0.0.1。官方示例默认是对所有网卡开放的 8000,如果直接暴露而不套 HTTPS,登录密码和会话都会明文传输。

出处

想要类似的东西?网站、Telegram 机器人、运维工具,说说你的需求。
发布需求

有想法或问题?去留言板留言 →