banner
约 1,800 字
6 分钟

NAS远程访问不安全?用Lucky给自己的服务增加手机二次验证

摘要

本文介绍利用 lucky 为 NAS 本地服务添加域名反代及二次验证的方法。首先通过 Cloudflare 和 lucky 设置 DDNS 与反向代理,随后在 lucky 中配置 GitHub 第三方认证,并可选绑定 Bitwarden 二步验证。访问服务时需登录 GitHub 并输入验证码,从而在不支持密码的服务上实现安全访问。注:此功能需安装完整版万吉。

前言

为了方便在外面也能访问家里 NAS 上的服务,我一直用 lucky(一款功能很全的反代/DDNS 工具)给 NAS 上的本地服务添加域名反代,通过 DDNS 实现外网访问。

但"方便"和"安全"往往是矛盾的:一旦把服务用域名暴露到公网,就等于把它开到了公网上,谁扫到都可能来试。为了安全,我通常会再给服务加一道登录密码。可麻烦的是——

  • 有些服务本身没有登录功能;

  • 有些服务我压根就不想让它暴露出去,却又需要在外网临时用一下。

这种情况下,给这些服务加一道"二次验证"(第三方认证),就是一个很好的兜底方案。今天以我在 NAS 上部署的一个搜书服务为例(这个项目本身没有登录功能,但又不想被陌生人访问),演示怎么用 lucky + GitHub 给它套上一层登录墙。

如果你还没用过 lucky,建议先看基础篇:用 lucky 插件通过 IPv6 访问本地服务器,把域名反代和 DDNS 跑通,这篇讲的是在它之上再加一层认证。

先区分清楚:你要的防护在哪一层

"服务暴露在公网"这件事,其实可以在三个不同层面上做防护,它们的职责完全不一样:

层面

防的是什么

典型手段

网络层

来自哪些 IP 的访问

路由器防火墙、只放行特定 IP 段

应用层

恶意的请求内容

WAF(拦截注入、扫描、恶意 payload)

认证层

谁有资格进来

登录密码、二次验证、OAuth

这篇做的是认证层——不管对方从哪来、请求长什么样,没通过身份验证就一律进不去。它解决的是"服务本身没有登录功能"这个最尴尬的场景。

顺带一提,lucky 自己也能配网络层和应用层的防护(IP 过滤 + WAF),那是另一套配置,我写在用 Lucky 保护你的飞牛:开启 WAF 防火墙、设置 IP 过滤这篇里。两者可以叠加:WAF 挡恶意请求,二次验证挡人。

前置:添加域名反代

先用 lucky 给服务做好域名反代(DDNS 的详细方法可以参考我之前的文章,这里快速过一遍流程):

  1. Cloudflare 添加解析:添加一条解析记录,指向 NAS 的 IPv6 地址。

blog image
  1. lucky 添加动态域名记录:在 lucky 的 DDNS 里配置这条域名记录,让外网 IP 变化时能自动更新解析。

blog image
  1. web 服务中添加反代规则:在 lucky 的 Web 服务里新增一条反代规则,把域名指向 NAS 上的搜书服务。

blog image
  1. 测试外网访问:配置好后先从外网访问一下,确认能正常打开。

blog image

到这里,服务和"能外网访问"已经打通了,但此刻它也是完全裸奔的——谁拿到域名都能进。接下来就给它上锁。

"谁知道域名啊,扫不到吧?" 这是最常见的侥幸心理。事实上,只要服务在公网 IP 上开放了端口,端口扫描是全网无差别进行的——不需要知道你的域名,扫到端口再反查 SNI(HTTPS 握手里的主机名)就能找到你。所以"用了个随机域名"根本不构成防护。这也正是这篇要解决的问题。

添加 GitHub 认证

1. 添加认证用户

在 lucky 里选择 第三方认证,先添加一个认证用户。这里的思路是用 GitHub 账号作为"入场券"——只有被你授权的 GitHub 账号才能通过认证。

blog image

2. 发起授权

添加用户后,点击 发起授权,lucky 会生成一个 GitHub OAuth 授权链接。

blog image

3. 登录 GitHub 并授权

在弹出的新页面里,登录你的 GitHub 账号并完成授权。授权成功后,这个 GitHub 账号就和 lucky 的认证用户绑定了,只有它能通过这道登录墙。

blog image

这一步到底发生了什么? 这是标准的 OAuth 授权流程:lucky 并不是拿到了你的 GitHub 密码,而是拿到了 GitHub 签发的"这个用户确实是他本人"的凭证。你的密码全程只输在 GitHub 自己的页面上,lucky 从来接触不到。 这也是为什么这种方案比"给服务设个固定密码"更安全——密码不存在被服务方泄露的可能。

反过来说:如果你哪天在 GitHub 里撤销了这次授权(GitHub 设置 → Applications),那么这条登录墙会立刻失效,你就进不去了。这是它的"开关"所在,记住这个位置。

4.(可选)添加二步验证

想更保险的话,还可以给认证加上二步验证(2FA)。这时需要用登录工具器(TOTP 扫码工具)扫码绑定——我选择用 Bitwarden 来管这个动态码。之后每次登录除了 GitHub 授权,还要再填一个 6 位动态验证码。

blog image

为什么 6 位数字能验证身份? 因为 TOTP(基于时间的一次性密码)的原理是:绑定的时候,服务器和你各存了一份相同的密钥;之后双方用「这份密钥 + 当前时间」按同一个算法算出同一个 6 位数,每 30 秒变一次。服务器只要看到你报的数字和它算出来的一致,就认为你持有密钥。所以这个码不需要联网传输,也不会被截获后长期复用。

⚠️ 由此引出一个常见坑:这个算法强依赖时间准确。如果你的 NAS 或手机时间偏移超过 30 秒,验证码就会永远对不上。遇到"动态码怎么输都不对",先去检查两端的时间同步(timedatectl 看 NTP 是否正常)。

5. 在反代规则上启用认证

回到 Web 服务的反代规则里,修改这条子规则:打开 网页认证,选择刚才绑定的那个 GitHub 账户。这样这条反代就挂上了"登录墙"。

blog image

安全登录效果

配置完成后再访问服务链接,就会看到 lucky 的登录页面:

  1. 打开服务链接,不再直接进入搜书服务,而是先跳到 lucky 的登录页面;

blog image
  1. 点击下面的 GitHub 图标(首次会要求登录 GitHub 并完成授权),授权通过;

blog image
  1. 如果开了二步验证,还要填 Bitwarden 生成的 6 位动态密码,全部通过后才进入服务。

blog image

两个容易被忽略的点

① 这层认证是"单点"——它挂了你自己也进不去。

认证由 lucky 负责,所以lucky 本身出问题(容器挂了、配置改错、证书过期)时,你从外网也会被挡在门外。这不是 bug,是架构决定的。

务实的做法:留一条不用 lucky 的备用通道。比如在内网直接访问服务的本地端口(http://192.168.1.x:端口),或者用 Tailscale 这类不依赖 lucky 的方式连进来。别让自己只有一个入口——我的做法是把这条内网直连路径写在密码管理器里,真出事时不用现场排查。

② "密码和动态码放同一个 App",其实削弱了 2FA 的意义。

用 Bitwarden 同时存密码和 TOTP,确实方便——但 2FA 的本意是"两个独立因素":你知道的东西(密码)+ 你有的东西(手机/密钥)。两个都放进同一个密码库,等于把两个因素又合并成了一个:一旦这个库被攻破,两道门一起失守。

我的取舍是:便利性在这个场景下更重要,所以我还是放在一起。但你应该清楚这个代价——如果你保护的是真正敏感的东西(比如银行、主邮箱),那 TOTP 值得单独放一个 App(Aegis、Ente Auth 之类)。顺带一提,我之前有篇讲甲骨文账号 2FA 迁移的实践:把甲骨文登录二次验证换成 Bitwarden 验证器,那里也讨论过同样的问题。

结尾提醒

⚠️ 需要注意:这个功能可能需要 lucky 的完整版(万吉)才支持;而且 3.0 版本的界面和 2.7 可能略有不同。如果你用的是精简版或旧版本,找不到对应选项是正常的。

个人结论

用 lucky 给"没有登录功能"的服务套一层 GitHub 认证 + 可选的二步验证,等于给裸奔的反代服务补上了一道像样的门禁:未授权的 GitHub 账号进不来,加上 2FA 后即使 GitHub 账号泄露也需要动态码才能过,安全度明显上了一个台阶。特别适合那些"不得不暴露、又没有自带认证"的自托管服务。缺点是第三方认证依赖 GitHub 可用性,但对于个人自托管场景,这个方案在安全与便利之间的平衡已经相当好了。值得一试。

如果你想再叠一层:认证层解决"谁能进",应用层(WAF)解决"进来的是恶意请求怎么办"。这两层不冲突——WAF 的具体配置可以看雷池 WAF 社区版:反代 + 免费防火墙,它是另一套独立于 lucky 的方案。

END