站点访问密码门 · 跨子域 SSO 机制
给一批私人动态站加的统一登录门,跨子域单点登录。本篇只讲机制与经验教训,不含任何密码、密钥、子域清单。
#一、整体机制:HMAC 令牌 + 父域 Cookie 实现 SSO
- 一套共享的「访问密码 + 签名密钥」环境变量:
SITE_PASSWORD(口令)+AUTH_SECRET(HMAC 签名密钥)。 - 登录流程(Next.js):
proxy.ts(中间件)拦截未登录请求 → 重定向到/login;/login页提交密码 →/api/login校验密码;- 校验通过后用
AUTH_SECRET签发一个 HMAC 令牌,写进 httpOnly Cookie; - 后续请求带 Cookie,中间件验签放行。
- 跨子域 SSO 的关键:Cookie 的
Domain设为父域(如.example.com),这样同父域下所有子站共享登录态,登录一次、全站免登录(有效期如 30 天)。 - 核心库集中在一个文件(如
lib/hub-auth.ts/lib/auth.ts),4 个门文件即可构成完整的门:lib/hub-auth.ts、proxy.ts、app/login/page.tsx、app/api/login/route.ts。
#二、给新站加门(3 步)
- 把这 4 个门文件拷进新项目(
src/app结构的放src/下):lib/hub-auth.ts、proxy.ts、app/login/page.tsx、app/api/login/route.ts。 - 在部署平台配置两个环境变量
SITE_PASSWORD和AUTH_SECRET——要 SSO 生效,所有同父域站点这两个值必须完全一致。 - 部署上线,并确保这 4 个门文件都进了 git 提交(走 Git 自动部署时尤其要确认,别把门文件漏掉、把门冲掉)。
#三、换密码 / 换密钥的语义
- 改
SITE_PASSWORD(所有站改成同一新值)→ 换登录密码。 - 改
AUTH_SECRET→ 让所有人当前登录立即失效(相当于强制全体登出)。
#四、独立门 vs 共享门
- 想把某个站单独发密码给某一个人用时,就给它一套独立的密码门:Cookie 只挂本域(而非父域),用独立的
SITE_PASSWORD/AUTH_SECRET。这样共享门的密码进不来、这把密码也进不了别的站,隔离清晰。
#五、静态站为什么加不了这种门
- 若 DNS 托管在 Vercel 免费版(vercel-dns),免费版无法给静态站的自定义域名加密码保护。
- 要锁静态站,两条路:把 DNS 迁到 Cloudflare 用 Cloudflare Access,或升级到 Vercel Pro 用原生 Password Protection。
- 因此实践中静态站维持公开(确保其中无敏感信息),只有动态站加门。
#六、最重要的教训:sensitive 环境变量必须本地留档
- 部署平台(Vercel)上标为 sensitive 类型的变量,写进去后 CLI、网页后台、REST API(即使带
?decrypt=true)三条路都读不回明文。 - 曾发生:某条写类命令(
vercel blob create-store)覆写了本地.env.local,几个 sensitive 值当场变成孤本丢失,只有靠浏览器密码管理器存的记录才找回一个。 - 铁律:
- 跑任何 vercel 写类命令前先
cp .env.local .env.local.bak; - 凡是自己生成的密钥/密码,先在本地/离线文档留一份,再写到平台上。
- 跑任何 vercel 写类命令前先
来源:配置信息/站点访问密码门.md(整理于 2026-08-18)