← [ BACK_TO_NOTES ]

Authentication模块

认证模块的职责与示意数据模型,区分身份验证、会话生命周期和资源授权。

#backend #concept #growing #auth

[!info] related notes

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)

  1. 用户提交 username + password。

  2. Auth Service 查询该用户的 password verifier(其中应包含或可解析出算法、salt、cost 等参数)。

  3. Auth Service 调用 PasswordHasher/Verifier 按 stored scheme 验证输入密码;不要在业务编排层写死某个 bcrypt cost。

  4. 比对成功:

    • 如果 verifier 参数已低于当前 policy,则执行 rehash-on-login,用当前 Argon2id/PBKDF2/bcrypt policy 更新 verifier。

    • 生成 Access Token (JWT)。

    • 生成 Refresh Token (随机字符串),并将 Hash 存入 refresh_tokens 表。

    • 写入 login_logs 表(成功)。

  5. 返回 Token Pair 给前端。

场景二:令牌刷新 (Refresh Flow)

这是用户无感知的关键步骤。

  1. 前端确认认证失败属于可刷新的 Access Token 过期情况;不是所有 401 都应触发刷新。

  2. 前端携带 Refresh Token 请求 /auth/refresh 接口。

  3. Auth Service 查库:

    • 该 Refresh Token 是否存在且 is_revoked = 0?
    • 是否在有效期内,关联 Session/Grant 和账户状态是否允许续期?
  4. 校验通过:

    • 生成新的 Access Token。
    • 若采用轮转,应原子消费旧 token 并创建替代值,处理并发、重放及 token family;不能仅生成新字符串。
  5. 返回 新的 Token。客户端合并并发刷新、限制重试,并评估原请求重试是否安全;刷新失败不能递归刷新。

场景三:鉴权 (Authorization / Guard)

这通常发生在网关层或拦截器中。

  1. 拦截所有请求,提取 Header 中的 Authorization: Bearer <token>。
  2. 解析 JWT,并使用对应公钥/对称密钥验证签名与标准 claims(如 exp/iss/aud);签名 JWT 不是“解密后再验证”。
  3. 凭据验证通过后建立 principal,并继续检查资源归属、tenant、scope、角色和业务状态;只有授权通过才能执行目标操作。

五、 必须注意的安全细节

在实现 Auth 模块时,有几个边界必须明确:

  1. Password verifier:

    • 不存明文,也不要直接保存 SHA-256(password)。
    • 新系统优先使用 Argon2id;合规/兼容场景可按策略使用 PBKDF2;已有 bcrypt 系统可渐进迁移。
    • 每个 verifier 使用独立 salt;成熟格式通常已把 salt、cost、algorithm/version 编入 stored string,不需要再维护一列“业务 salt”。
    • Password hashing 主要提高数据库泄漏后的 offline guessing 成本;在线登录仍必须有 rate limiting。
  2. JWT 不要存敏感信息:

    • 千万别把用户的手机号、身份证号放在 JWT Payload 里。签名 JWT 的载荷只是 Base64url 编码,持有者可以解码;修改后的内容必须因签名验证失败而被拒绝。
  3. Refresh Token 的存储:

    • 数据库里存 Hash,不要存明文。对于高熵随机 token,哈希可降低数据库只读泄漏后的直接重放风险,但不替代应用、会话存储和数据库写权限保护。
  4. 浏览器端 Token 存储:

    • Access Token: 可以按架构选择内存或 Cookie;localStorage 会被同源 JavaScript/XSS 读取,不能称为“最安全”默认方案。
    • Refresh Token: 浏览器场景常通过 HttpOnly; Secure; SameSite=... Cookie 降低 JavaScript 直接窃取风险;与此同时必须按 Cookie 模式处理 CSRF、SameSite 与 rotation/replay detection。

总结

Auth 的职责是建立并维护可信身份上下文;状态规模取决于会话数量和安全策略,不能预设它一定无状态或只有少量存储。

  • Account 模块提供了“我是谁”的数据。
  • Auth 模块提供了“证明我是谁”的能力。

学习检查

  1. 为什么验证 JWT 签名不能替代资源授权?
  2. 撤销 Refresh Token 后,已发 Access Token 为什么可能仍可用?
  3. 为什么模块分离不等于数据库分离?
创建于 2025/1/1 更新于 2026/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本