Authentication模块

开发中认证模块实现。包含认证相关功能:邮箱认证、登录认证(身份认证)

#tech / dev / desktop #type / concept #status / growing #resource / electron

[!info] related notes

Authentication模块

[!info] related notes

Overview

我们继续深入。如果说 Account 模块是**“档案局”(存数据的),那么 Authentication(Auth)模块就是“安检处”**(验证数据的)。

Auth 模块的核心目标只有三个:验证身份发放通行证(Token)维护通行证的状态


核心服务拆分 (Services)

Auth 模块通常包含以下几个核心 Service,它们不直接管理用户资料,而是处理“验证”逻辑:

1. 认证服务 (Authentication Service)

这是总入口,负责编排验证流程。

  • login(credentials): 接收用户名/密码或验证码。调用存储的 Hash,然后进行比对(Bcrypt/Argon2)。
  • logout(token): 注销登录,销毁服务端保存的 Refresh Token。
  • validate(token): 校验 Token 的有效性(通常由 API 网关或中间件完成,但逻辑在 Auth 模块)。

2. 令牌服务 (Token Service)

专门负责生成和解析“通行证”。

  • issueToken(userId, scopes): 生成 Access Token (JWT) 和 Refresh Token。
  • refreshToken(refreshToken): 用旧的 Refresh Token 换取新的 Access Token。这是维持用户“长期在线”的关键。
  • revokeToken(userId): 强制让某个用户下线(将 Refresh Token 加入黑名单或删除)。

3. 第三方登录服务 (OAuth Service)

处理“用微信/Google登录”的复杂逻辑。

  • generateAuthUrl(provider): 生成跳转到第三方授权页面的 URL。
  • callback(provider, code): 接收第三方的回调 Code,换取 Access Token 和 OpenID,然后通过 OpenID 查找系统内的 userId。

数据库设计 (Database Schema)

虽然 Auth 模块尽量做到“无状态”(Stateless),但为了安全和管理,我们必须存储长期的会话状态

认证信息表(auth_credentials)

把认证信息表放在 authentication 模块虽然安全,但会带来一个复杂性挑战:分布式事务(数据一致性)。 因为注册一个用户,现在需要同时写两个数据库。 简单点的话应该直接把这个表放在 account 模块。

CREATE TABLE auth_credentials (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,        -- 关联 Account 模块的全局唯一 ID
    type VARCHAR(20) NOT NULL,      -- 类型: PASSWORD, OAUTH_TOKEN, API_KEY
    identifier VARCHAR(255) NOT NULL, -- 登录名: 邮箱/手机号/OpenID (需建立唯一索引)
    credential VARCHAR(500),        -- 密码Hash 或 第三方Token
    salt VARCHAR(100),              -- (可选) 如果Hash算法需要单独存盐
    version INT DEFAULT 1,          -- 凭证版本 (用于改密后踢出旧设备)
    created_at DATETIME,
    
    UNIQUE INDEX idx_ident (identifier) -- 确保登录名唯一
);

刷新令牌表 (refresh_tokens)

这是 Auth 模块最核心的表

JWT (Access Token) 是不存数据库的,但 Refresh Token 必须入库,以便我们能随时“撤销”用户的登录状态。

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)

这是 Access Token 内部包含的数据,会被 Base64 编码传给前端。

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 调用 Account 模块查询该用户的 credential (密码 Hash)。

  3. Auth Service 使用 bcrypt.verify(inputPassword, dbHash) 进行比对。

  4. 比对成功

    • 生成 Access Token (JWT)。
    • 生成 Refresh Token (随机字符串),并将 Hash 存入 refresh_tokens 表。
    • 写入 login_logs 表(成功)。
  5. 返回 Token Pair 给前端。

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

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

  1. 前端发现 API 返回 401 Unauthorized (Token 过期)。

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

  3. Auth Service 查库:

    • 该 Refresh Token 是否存在且 is_revoked = 0
    • 是否在有效期内?
  4. 校验通过

    • 生成的 Access Token。
    • (可选) 轮转 Refresh Token:生成一个新的 Refresh Token,废弃旧的(安全性更高)。
  5. 返回 新的 Token 给前端,前端重试刚才失败的请求。

场景三:鉴权 (Authorization / Guard)

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

  1. 拦截所有请求,提取 Header 中的 Authorization: Bearer <token>
  2. 解析 JWT(不需要查库,直接用公钥/密钥解密验证签名)。
  3. 如果签名正确且未过期 -> 放行,并将 userId 注入到 Request Context 中供后续业务使用。

五、 必须注意的安全细节

在实现 Auth 模块时,有几个“坑”千万别踩:

  1. JWT 不要存敏感信息:

    • 千万别把用户的手机号、身份证号放在 JWT Payload 里。因为 JWT 只是 Base64 编码,任何人都能解码看到内容(虽然他们无法篡改)。
  2. Refresh Token 的存储:

    • 数据库里存 Hash,不要存明文。如果数据库泄露,黑客拿不到明文 Token,就无法模拟用户刷新。
  3. 前端怎么存 Token?

    • Access Token: 存在内存变量里(最安全,防 XSS),或者 localStorage。
    • Refresh Token: 建议存在 HttpOnly Cookie 中。这样 JavaScript 读不到,能有效防止 XSS 攻击窃取 Refresh Token。

总结

Auth 模块是系统的无状态逻辑中心 + 少量状态存储(Session/RefreshToken)

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

我可以为你做的下一步:

我们可以接着讨论 RBAC(基于角色的权限控制),即“登录之后,怎么控制用户能访问哪些菜单或按钮”?或者你想看一段 JWT 生成与验证的代码示例

创建于 2025/1/1 更新于 2026/5/27