| name | auth-constraints |
| description | 认证与会话约束(authN,不含 authZ)——双 token、refresh 原地轮换 + 重放即吊销、滑动续期、密码 argon2id。触发:实现/设计登录注册、token 签发/刷新/校验、会话或设备管理、密码哈希、登出/吊销、前端 token 携带与刷新。跳过:权限判定(→authz-constraints)、第三方 SSO 托管、与认证无关的业务端点。 |
认证与会话约束(authN)
适用于:登录/登出、token 签发与校验、会话与设备管理、密码存储等认证相关的设计、实现、文档与 review。只做认证(authN),不含授权(authZ)——RBAC / 资源 ownership / 权限模型不在本约束范围。
定位:技术框架定型,钉死「机制骨架」(【硬】必守),不绑「业务策略」(【软】给能力、由项目按业务选)。
0. 触发与跳过
TRIGGER:实现/设计登录注册、token 签发/刷新/校验、会话或设备管理、密码哈希、登出/吊销;前端请求层的 token 携带与自动刷新;文档 / PRD / 设计 / review 涉及认证。
SKIP:授权/权限判定(authZ)、纯第三方 SSO 托管(不自管会话)、与认证无关的业务端点。
1. 双 token 机制【硬】
| token | 形态 | TTL | 内容 / 落库 |
|---|
| access | JWT(无状态、不查库) | 5–15min【软取值】 | 最小 claim:sub(user_id)、device_id、jti、iat、exp;禁塞敏感信息 |
| refresh | 不透明随机串(≥256 bit) | 7–30d【软取值】 | 落库,且只存哈希(如 SHA-256),禁存明文 |
- 滑动续期【硬机制】:每次用 refresh 刷新时把
expires_at 顺延到 now + refresh TTL。只要用户活跃间隔 < refresh TTL,会话滚动有效。
- 会话最长寿命【硬决策 / 软取值】:项目必须就「会话是否设绝对上限」显式决策,框架不替你选、但禁糊里糊涂:
- 设上限 →
absolute_expires_at = 首次登录起最长寿命(如 30–90d),与滑动续期取先到,到点强制重登。
- 显式不设(
absolute_expires_at 留空)→ 活跃即永久,活跃用户永不被动踢出(消费级常见)。此时必须靠 rotation + 重放检测(第 2 节)兜泄露——长期有效的 refresh 被盗后才可被察觉并吊销。
2. refresh 会话落库 + 轮换【硬】
- 一行 / 会话,原地更新:一个
(user_id, device_id) 登录会话就是一行。rotation = 在同一行上把 refresh_token_hash 换新、expires_at 顺延,禁插新行——表大小 = 活跃设备数,与刷新次数无关。
- 轮换 + 宽限窗口 + 泄露吊销:保留上一个 token 哈希(
prev_token_hash)以容错网络重试 / 并发。收到 refresh,比对哈希分三种:
| 命中 | 判定 | 动作 |
|---|
= refresh_token_hash(current) | 正常刷新 | prev ← current、current ← 新、rotated_at ← now、顺延 expires_at,返回新 token 对 |
= prev_token_hash 且 now - rotated_at ≤ 宽限窗口(如 30–60s【软取值】) | 网络重试 / 并发,非攻击 | 幂等放行、签发可用 token,不踢人 |
| 两者都不命中,或 = prev 但超窗口 | 真重放 / 泄露 | 吊销该 (user_id, device_id) 全部会话(置 deleted) |
- 会话表遵循
database-constraints(UUIDv7 主键、deleted 软删除、DB 管理时间戳、全链路 UTC)。deleted != 0 表达会话作废/登出/吊销;有效会话 = deleted = 0 AND now < expires_at AND (absolute_expires_at IS NULL OR now < absolute_expires_at)。
CREATE TABLE auth_session (
id BINARY(16) NOT NULL,
user_id BINARY(16) NOT NULL,
device_id VARCHAR(128) NOT NULL,
refresh_token_hash BINARY(32) NOT NULL,
prev_token_hash BINARY(32) NULL,
rotated_at DATETIME(6) NULL,
user_agent VARCHAR(512) NULL,
expires_at DATETIME(6) NOT NULL,
absolute_expires_at DATETIME(6) NULL,
deleted BIGINT NOT NULL DEFAULT 0,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
UNIQUE KEY uk_refresh (refresh_token_hash, deleted),
KEY idx_user_device (user_id, device_id, deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
3. 设备识别【硬机制】
device_id = 客户端持有的稳定唯一设备标识,首次确定后持久化(Web 存本地、移动端存原生存储),每次刷新随 refresh 绑定上送。要求稳定、唯一、客户端持久化,格式不强制 UUID——Web 可自生成 UUIDv4,移动端可用平台 ID(Android ID、iOS identifierForVendor)或自生成随机串。
- UA 仅解析用于「展示」(给用户看「Chrome on macOS」),禁用于任何安全判定——UA 正被浏览器精简(UA Reduction)且易伪造/雷同。
4. 多设备策略【软 — 项目自选】
同一套 auth_session 机制天然支持三种策略,skill 给能力、不强制选哪种:
| 策略 | 实现(同一套机制) |
|---|
| 多设备并存 | 每 (user_id, device_id) 一条有效会话,互不影响 |
| 单设备互踢 | 新登录时把该 user 其余会话 deleted 掉 |
| 限最多 N 台 | 登录时统计有效会话数,超 N 删最旧 |
5. 密码与登录凭证【硬下限】
- 哈希用 argon2id:推荐
m=19456 KiB, t=2, p=1 起步【软参数,按硬件压测调】;禁明文 / MD5 / SHA1 / 无盐快速哈希。
- 登录失败限流 / 锁定【硬机制】:失败计数 + 退避或锁定防爆破,阈值与策略【软】由项目定。
6. token 传输与存储【硬】
- 传输:
Authorization: Bearer <access>(对齐 api-design);refresh 仅在专用刷新端点提交,禁随业务请求广播。
- 存储:Web =
localStorage、移动端 = Keychain / Keystore 等原生安全存储;禁把明文 token 写日志 / URL query。
7. 前端刷新流程【硬机制】
- access 放运行时内存,请求拦截器附加
Authorization: Bearer <access>。
- 响应
401(access 过期)→ 调刷新端点用 refresh 换新 token 对 → 重放原请求。
- 并发 single-flight:多个请求同时 401 只发起一次刷新,其余复用同一刷新结果,禁并发打爆刷新端点。
- 刷新失败(refresh 也失效)→ 清本地凭证 → 跳登录。
8. 登出与吊销【硬机制 / 软范围】
| 操作 | 实现 |
|---|
| 单设备登出 | 当前会话 deleted |
| 全端登出 | 该 user 所有会话 deleted |
| 管理员踢出 | 指定会话 / 用户 deleted |
JWT access 的取舍:access 无状态,吊销后仍有效至自然过期(≤ access TTL),靠短 TTL 收敛窗口。如需即时吊销 access,需另上 jti 黑名单——本框架默认不做,需要时项目自行扩展。
9. 错误码【硬】
认证类错误归入一个模块号,统一走 api-design 的 A-BBB-CCCC,禁另起体系。模块号 BBB 由项目按 api-design 分配(下表占位 0xx):
| 场景 | HTTP | code 示例 |
|---|
| access 过期 | 401 | 1-0xx-0001 |
| token 签名无效 | 401 | 1-0xx-0002 |
| refresh 失效 / 已轮换 / 重放 | 401 | 1-0xx-0003 |
| 设备不匹配 | 401 | 1-0xx-0004 |
| 账号或密码错误 | 401 | 1-0xx-0005 |
| 登录失败过多 / 锁定 | 429 | 1-0xx-0006 |
错误响应禁泄露内部细节(是否存在该账号、stack trace 等)。
10. 自检清单