Authentication模块
开发中认证模块实现。包含认证相关功能:邮箱认证、登录认证(身份认证)
[!info] related notes
Authentication模块
[!info] related notes
- 相关业务: Account模块, 认证业务
- 认证与登录: Token认证的实现流程, API认证与安全, Cookie、Session、Token、JWT 的区别
- 关联实现: express实现登录, 账号信息记录功能实现
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)
-
用户提交
username+password。 -
Auth Service 调用 Account 模块查询该用户的
credential(密码 Hash)。 -
Auth Service 使用
bcrypt.verify(inputPassword, dbHash)进行比对。 -
比对成功:
- 生成
Access Token(JWT)。 - 生成
Refresh Token(随机字符串),并将 Hash 存入refresh_tokens表。 - 写入
login_logs表(成功)。
- 生成
-
返回 Token Pair 给前端。
场景二:令牌刷新 (Refresh Flow)
这是用户无感知的关键步骤。
-
前端发现 API 返回
401 Unauthorized(Token 过期)。 -
前端携带
Refresh Token请求/auth/refresh接口。 -
Auth Service 查库:
- 该 Refresh Token 是否存在且
is_revoked = 0? - 是否在有效期内?
- 该 Refresh Token 是否存在且
-
校验通过:
- 生成新的 Access Token。
- (可选) 轮转 Refresh Token:生成一个新的 Refresh Token,废弃旧的(安全性更高)。
-
返回 新的 Token 给前端,前端重试刚才失败的请求。
场景三:鉴权 (Authorization / Guard)
这通常发生在网关层或拦截器中。
- 拦截所有请求,提取 Header 中的
Authorization: Bearer <token>。 - 解析 JWT(不需要查库,直接用公钥/密钥解密验证签名)。
- 如果签名正确且未过期 -> 放行,并将
userId注入到 Request Context 中供后续业务使用。
五、 必须注意的安全细节
在实现 Auth 模块时,有几个“坑”千万别踩:
-
JWT 不要存敏感信息:
- 千万别把用户的手机号、身份证号放在 JWT Payload 里。因为 JWT 只是 Base64 编码,任何人都能解码看到内容(虽然他们无法篡改)。
-
Refresh Token 的存储:
- 数据库里存 Hash,不要存明文。如果数据库泄露,黑客拿不到明文 Token,就无法模拟用户刷新。
-
前端怎么存 Token?
- Access Token: 存在内存变量里(最安全,防 XSS),或者 localStorage。
- Refresh Token: 建议存在 HttpOnly Cookie 中。这样 JavaScript 读不到,能有效防止 XSS 攻击窃取 Refresh Token。
总结
Auth 模块是系统的无状态逻辑中心 + 少量状态存储(Session/RefreshToken)。
- Account 模块提供了“我是谁”的数据。
- Auth 模块提供了“证明我是谁”的能力。
我可以为你做的下一步:
我们可以接着讨论 RBAC(基于角色的权限控制),即“登录之后,怎么控制用户能访问哪些菜单或按钮”?或者你想看一段 JWT 生成与验证的代码示例?