Authentication模块
认证模块的职责与示意数据模型,区分身份验证、会话生命周期和资源授权。
[!info] related notes
- 所属 MOC: 后端开发 MOC
- 相关业务: Account模块, 认证业务
- 认证与登录: Token认证的实现流程, API认证与安全, Cookie、Session、Token、JWT 的区别
- Password: Password Policy, Password Storage & Verification, Argon2id, bcrypt
- 在线攻击/防御: Credential Stuffing / Spraying / Brute Force, Authentication Throttling & Account Lockout
- MFA: Password 与 MFA 的安全边界, MFA Recovery Codes
- Recovery/Lifecycle: Password Reset & Account Recovery, Email Verification & Email Change Security, Credential Change & Session Revocation
- Session Trust: Active Sessions & Device Management, Trusted Device & Remember MFA, Reauthentication Freshness
- Security Events/Risk: Authentication Security Events & Login History, Risk-Based Authentication
- Electron 相关: electron-vue3-multi-account-ipc-identifier, electron-vue3-dual-window, [[z/readme|README]]
- 关联实现: express实现登录, 账号信息记录功能实现
Authentication模块
Overview
本页维护认证模块的通用职责与示意存储模型,不声明 Electron 或任何具体项目已经按此实现。完整业务生命周期见 authentication-business,Access / Refresh 协议细节见 token-auth-implementation-flow。
Auth 模块负责验证身份、建立可信会话并控制其生命周期;认证恢复、MFA 和风险决策也属于这个边界。不是所有实现都需要 JWT。
核心服务拆分 (Services)
Auth 模块通常包含以下几个核心 Service,它们不直接管理用户资料,而是处理“验证”逻辑:
1. 认证服务 (Authentication Service)
这是总入口,负责编排验证流程。
login(credentials): 接收用户名/密码或验证码。密码登录时读取 stored password verifier,通过 password verification 比对;算法由 verifier 自身的 scheme/parameters 决定,而不是在业务层写死 bcrypt。logout(token): 撤销当前会话或 refresh family,并清除客户端凭据;已发 Access Token 的剩余有效期需按架构处理。validate(token): 校验 Token 的有效性(通常由 API 网关或中间件完成,但逻辑在 Auth 模块)。
2. 令牌服务 (Token Service)
专门负责生成和解析“通行证”。
issueToken(userId, scopes): 在采用 token 架构时签发访问与续期凭据,Access Token 可为 JWT 或 opaque token。refreshToken(refreshToken): 用旧的 Refresh Token 换取新的 Access Token。这是维持用户“长期在线”的关键。revokeToken(userId): 按范围撤销会话或 refresh family;删除 Refresh Token 不自动使离线验证的 JWT 立即失效。
3. 第三方登录服务 (OAuth Service)
处理“用微信/Google登录”的复杂逻辑。
generateAuthUrl(provider): 生成跳转到第三方授权页面的 URL。callback(provider, code): 校验回调上下文并交换凭据;使用 OIDC 时验证 ID Token 的签名、issuer、audience、nonce 等,再以 provider / issuer 与 subject 的组合映射账户。OAuth 授权本身不等于完成登录认证。
数据库设计 (Database Schema)
存储需求由认证架构决定。以下采用需要服务端撤销和续期控制的 Hybrid 示例。
认证信息表(auth_credentials)
凭据由哪个模块封装,不决定它必须使用独立数据库。同库可以用本地事务;分库才需要额外的一致性设计。不能为了回避假设中的分布式事务,就把所有凭据直接放入可公开读取的账户资料。
可按凭据类型存储:
- 密码:用户关联、自描述 password verifier、凭据版本。
- 外部身份:provider / issuer 与 subject 的稳定组合,并建立匹配身份规则的唯一约束。
- API key 或会话凭据:适当的校验值、用途、到期与撤销状态。
手机号、邮箱、OAuth subject 的命名空间和规范化不同,不宜用一列裸 identifier 的全局唯一索引表达所有身份。原来的单表 SQL 只是设计草图,完整约束要由具体身份模型决定。
刷新令牌表 (refresh_tokens)
这是该 Hybrid 示例的会话状态之一。
在需要撤销和轮转的 Hybrid 设计中,要有服务端 Session/Grant 或 refresh 状态。JWT 可以本地验证,但是否保存其标识或撤销记录取决于架构;Refresh Token 也不一定存关系数据库。以下 SQL 只展示相关字段,生产 schema 还需 NOT NULL、关联、唯一性、索引与原子轮转约束。
CREATE TABLE refresh_tokens (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL, -- 归属用户
token_hash VARCHAR(255), -- 令牌哈希 (安全起见,数据库也不存明文 Token)
device_info VARCHAR(255), -- 设备信息 (如 "iPhone 13 - Chrome")
ip_address VARCHAR(45), -- 登录 IP
expires_at DATETIME, -- 过期时间 (通常设为 7-30 天)
is_revoked BOOLEAN DEFAULT 0, -- 是否被撤销 (用户登出或被踢下线时置为 1)
created_at DATETIME
);
登录日志表 (login_logs)
用于风控和审计。
CREATE TABLE login_logs (
id BIGINT PRIMARY KEY,
user_id BIGINT, -- 尝试登录的用户 (如果登录失败可能为空)
identifier VARCHAR(255), -- 尝试登录的账号 (如输入的手机号)
login_type VARCHAR(20), -- 方式: password, sms, google
status TINYINT, -- 0-失败, 1-成功
fail_reason VARCHAR(255), -- 失败原因 (如 "密码错误", "账号锁定")
ip_address VARCHAR(45),
user_agent VARCHAR(500),
created_at DATETIME
);
核心对象 (Domain Objects)
1. 载荷对象 (JWT Payload / Claims)
以下是签名 JWT 的示意 claims,载荷使用 Base64url 编码,不提供内容保密。读取它不等于验证它;不同服务还需核对预期 audience 等约束。
interface JwtPayload {
sub: string; // Subject (UserId)
name: string; // 用户名 (方便前端显示,不用再查一次库)
role: string; // 角色 (admin, user)
exp: number; // 过期时间戳
iat: number; // 签发时间戳
iss: string; // 签发者
}
2. 令牌对 (Token Pair - DTO)
登录成功后返回给前端的标准结构。
{
"access_token": "eyJh... (有效期 15分钟)",
"token_type": "Bearer",
"refresh_token": "rt_... (有效期 7天)",
"expires_in": 900
}
关键业务流程:登录与鉴权
场景一:用户登录 (Login Flow)
-
用户提交
username+password。 -
Auth Service 查询该用户的 password verifier(其中应包含或可解析出算法、salt、cost 等参数)。
-
Auth Service 调用 PasswordHasher/Verifier 按 stored scheme 验证输入密码;不要在业务编排层写死某个 bcrypt cost。
-
比对成功:
-
如果 verifier 参数已低于当前 policy,则执行 rehash-on-login,用当前 Argon2id/PBKDF2/bcrypt policy 更新 verifier。
-
生成
Access Token(JWT)。 -
生成
Refresh Token(随机字符串),并将 Hash 存入refresh_tokens表。 -
写入
login_logs表(成功)。
-
-
返回 Token Pair 给前端。
场景二:令牌刷新 (Refresh Flow)
这是用户无感知的关键步骤。
-
前端确认认证失败属于可刷新的 Access Token 过期情况;不是所有 401 都应触发刷新。
-
前端携带
Refresh Token请求/auth/refresh接口。 -
Auth Service 查库:
- 该 Refresh Token 是否存在且
is_revoked = 0? - 是否在有效期内,关联 Session/Grant 和账户状态是否允许续期?
- 该 Refresh Token 是否存在且
-
校验通过:
- 生成新的 Access Token。
- 若采用轮转,应原子消费旧 token 并创建替代值,处理并发、重放及 token family;不能仅生成新字符串。
-
返回 新的 Token。客户端合并并发刷新、限制重试,并评估原请求重试是否安全;刷新失败不能递归刷新。
场景三:鉴权 (Authorization / Guard)
这通常发生在网关层或拦截器中。
- 拦截所有请求,提取 Header 中的
Authorization: Bearer <token>。 - 解析 JWT,并使用对应公钥/对称密钥验证签名与标准 claims(如 exp/iss/aud);签名 JWT 不是“解密后再验证”。
- 凭据验证通过后建立 principal,并继续检查资源归属、tenant、scope、角色和业务状态;只有授权通过才能执行目标操作。
五、 必须注意的安全细节
在实现 Auth 模块时,有几个边界必须明确:
-
Password verifier:
-
JWT 不要存敏感信息:
- 千万别把用户的手机号、身份证号放在 JWT Payload 里。签名 JWT 的载荷只是 Base64url 编码,持有者可以解码;修改后的内容必须因签名验证失败而被拒绝。
-
Refresh Token 的存储:
- 数据库里存 Hash,不要存明文。对于高熵随机 token,哈希可降低数据库只读泄漏后的直接重放风险,但不替代应用、会话存储和数据库写权限保护。
-
浏览器端 Token 存储:
- Access Token: 可以按架构选择内存或 Cookie;localStorage 会被同源 JavaScript/XSS 读取,不能称为“最安全”默认方案。
- Refresh Token: 浏览器场景常通过
HttpOnly; Secure; SameSite=...Cookie 降低 JavaScript 直接窃取风险;与此同时必须按 Cookie 模式处理 CSRF、SameSite 与 rotation/replay detection。
总结
Auth 的职责是建立并维护可信身份上下文;状态规模取决于会话数量和安全策略,不能预设它一定无状态或只有少量存储。
- Account 模块提供了“我是谁”的数据。
- Auth 模块提供了“证明我是谁”的能力。
学习检查
- 为什么验证 JWT 签名不能替代资源授权?
- 撤销 Refresh Token 后,已发 Access Token 为什么可能仍可用?
- 为什么模块分离不等于数据库分离?