POST,数据存在 D1 的 admin_auth / admin_sessions / admin_fails 三张表。
首次设置密码
201):
BLOG_ADMIN_SETUP_KEY)为可选项,分两种模式:
密码使用 PBKDF2-SHA256(16 字节随机盐,100,000 次迭代,Cloudflare WebCrypto 的硬上限)加盐哈希后存 D1,永不存明文。初始化成功后
must_change 会写成 0。登录
expiresIn 为秒数(604800 = 7 天),Token 是 32 字节随机数(64 位十六进制),写入 D1 admin_sessions(exp 存绝对时间戳)。
未初始化时的登录行为
登录限流(三层维度 + 短冷却)
登录失败按三个维度独立计数,都只做短冷却,不做全局长锁定:- 为什么不做全局长锁定:全局长锁定等于把可用性交给攻击者——任何人刷够失败次数就能把唯一的管理员锁在门外,属于可用性 DoS。因此全局只冷却 10 秒(对齐 Cloudflare 免费版限流规则的最小窗口)。
- 计数 1 小时老化;冷却结束后保留已达阈值的计数一小段时间,避免「等冷却归零再无限重试」。
- 被限流时直接 429 早退,既不查库也不写库(顺带挡住「用失败登录刷爆 D1 免费写额度」)。
- 请求仍会跑 PBKDF2 之外的短路判断,但限流期间不会执行密码哈希,避免把「登录 DoS」变成「CPU / 额度 DoS」。
- 登录成功时清空本 IP / 本子网 / 全局三层计数,并顺带清理老化计数行与过期会话。
- 提示文案按剩余时间自动切换:不足 1 分钟显示秒,否则显示分钟(如
尝试次数过多,请 10 秒后再试)。 - 可选叠加 Workers 边缘限流绑定:配置后先过
env.LOGIN_LIMITER(每 IP 10 次/分钟/位置),被拦下的请求不查库不写库;未配置时行为与旧版完全一致(代码检测不到绑定就回退 D1 计数)。
应急通道(break glass)
登录请求携带正确的X-Setup-Key(即 BLOG_ADMIN_SETUP_KEY)时:
- ✅ 跳过全部三层限流 + 边缘限流
- ❌ 不跳过密码校验——密码错了照样
401「密码错误」 - 应急通道下的失败不计数,避免站长自己把正常路径刷爆
429 时会自动展开「被限流?用安装密钥登录」入口,填入安装密钥即可立即进入。这保证「攻击者最多让你 10 秒登不进,但永远挡不死你」。
登出
admin_sessions 行(Token 立即作废),返回 { "ok": true }。前端 cloudLogout 会先清本地 token 再调用。
修改密码
admin_sessions),必须重新登录。
会话自检
v2.10.100 新增(功能开关清单已同步至 v2.10.103)。 供前端在渲染「仅管理员可见」的界面元素前确认身份——目前用于 AI 摘要的「重新生成」按钮:游客页面里即使还残留着过期的本地 token,服务端确认不是管理员后按钮也不会渲染。
- 有效 token →
authed: true;缺失或过期 →authed: false(仍返回 200,便于前端直接判断而不必处理异常分支)
认证方式
所有需要认证的接口在请求头中携带 Token:- Token 存在 D1
admin_sessions(token、exp),有效期 7 天 - 前端存于
localStorage(键qingyu.token),由apiFetch自动附带;返回401时自动登出并跳回登录页 - 会话过期时读路径会顺手删除该行,避免
admin_sessions无限增长 - 兼容旧的静态长令牌:环境变量
BLOG_WRITE_TOKEN仍可作为Bearer令牌使用(常量时间比较)
客户端 IP 只取 Cloudflare 注入的
CF-Connecting-IP,不使用可被伪造的 X-Forwarded-For——否则攻击者构造该头即可绕过全部按 IP 的限流。想从源头再加一道门(Cloudflare Access / WAF 限流规则 / Workers 限流绑定三种方案),见 Cloudflare 配置完全指南 · 第 9 节。