Auth 鉴权(类 Sa-Token)
core/Auth 提供会话式登录鉴权:中间件只负责解析 token、注入请求上下文,不强制登录;业务侧用 AuthUtil 做登录、注销、校验角色/权限。设计对齐 Sa-Token 常见配置项。
目录
1. 注册与配置
在 src/index.ts 中注册:
import Auth from "./core/Auth";
Application.registry(
Auth({
tokenName: "Authorization",
header: "Authorization",
tokenPrefix: "",
timeout: 30 * 24 * 60 * 60 * 1000, // 30 天;-1 永不过期
activityTimeout: -1, // 闲置超时;-1 不启用
allowConcurrentLogin: false, // false:新登录挤掉同账号旧登录
isShare: false, // false:每次登录新建 token
tokenStyle: "uuid", // 见下文 Token 风格
// tokenStyle: "jwt" 时:
// jwtSecret: process.env.JWT_SECRET || "change-me",
// jwtAlgorithm: "HS256",
// redis: Redis, // 传入则会话走 Redis
// createToken: async ({ loginId, device }) => "...", // 自定义生成
// readCookie: true,
ignore: [
"/api/user/login",
"/api/user/check_login",
"/api/sys_config/public",
"/api/aes/key",
],
})
);AuthOptions 字段一览
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
tokenName | string | "satoken" | Token 名称(请求头 / Cookie 名) |
header | string | "Authorization" | 兼容旧配置的备用请求头 |
tokenPrefix | string | "Bearer" | 前缀;设为 "" 表示无前缀(项目默认) |
timeout | number | 2h(ms) | 绝对有效期;-1 永不过期 |
activityTimeout | number | -1 | 闲置超时(ms);-1 不启用 |
allowConcurrentLogin | boolean | true | 是否允许多端同时在线 |
isShare | boolean | false | 同账号是否复用已有有效 token |
tokenStyle | TokenStyle | "uuid" | Token 生成风格 |
createToken | (payload) => string | Promise<string> | — | 自定义生成(优先于 tokenStyle) |
jwtSecret | string | 内置默认串 | tokenStyle=jwt 时使用 |
jwtAlgorithm | string | "HS256" | JWT 算法 |
ignore | string[] | [] | 不注入鉴权上下文的路径(支持尾部 *) |
redis | Redis 封装 | — | 传入则会话走 Redis |
readCookie | boolean | true | 是否从 Cookie 读 token |
另有内置忽略:/.well-known、/favicon.ico、/robots.txt、/sitemap.xml。
2. Token 读取顺序
AuthUtil.readTokenFromRequest(req) 按以下顺序解析:
tokenName请求头(如Authorization: xxx)header请求头(若与tokenName同名则跳过,避免重复读取)- Cookie[
tokenName](readCookie !== false时)
若配置了 tokenPrefix(如 Bearer),会自动去掉前缀再取 token。当前项目 tokenPrefix: "",前端可直接传裸 token。
3. Token 风格
类型 TokenStyle:
| 值 | 说明 |
|---|---|
uuid | 标准 UUID(带横线) |
simple-uuid | 去掉横线的 UUID |
random-32 | 32 位 hex |
random-64 | 64 位 hex |
random-128 | 128 位 hex |
tik | 短可读风格,如 a1b2c3_d4e5f6_789abc |
jwt | 使用 jsonwebtoken 签发(需 jwtSecret) |
优先级:createToken > jwt > 其它 tokenStyle。
JWT 校验:加载会话前会 jwt.verify;非法则清理并视为未登录。非 jwt 风格不校验签名,以会话存储为准。
Auth({
tokenStyle: "jwt",
jwtSecret: process.env.JWT_SECRET || "change-me",
jwtAlgorithm: "HS256",
});自定义:
Auth({
createToken: async ({ loginId, device }) => {
return `custom_${loginId}_${device ?? "web"}_${Date.now()}`;
},
});4. 会话与超时
会话结构 AuthSession:
| 字段 | 说明 |
|---|---|
token | 当前会话 token |
loginId | 登录账号(string | number) |
device | 设备标识(多端) |
roles / permissions | 角色、权限列表 |
username / avatar | 展示信息(写操作日志等) |
createTime | 创建时间(ms) |
expireTime | 绝对过期时间(ms);timeout=-1 时为极大值 |
lastActiveTime | 最近活跃时间(ms),用于闲置超时 |
绝对超时 timeout
登录时:expireTime = now + timeout(timeout < 0 则永不过期)。每次请求中间件会 loadSession,过期则清理。
闲置超时 activityTimeout
activityTimeout >= 0 时:若 now - lastActiveTime > activityTimeout,视为过期。
有效请求会 touch 刷新 lastActiveTime(及 Redis TTL)。
5. 并发登录与共享 Token
allowConcurrentLogin
| 值 | 行为 |
|---|---|
true(默认) | 同一账号可多端同时在线 |
false | 新登录会挤掉旧登录:有 device 时只踢同设备;无 device 时踢掉该账号全部会话 |
isShare
| 值 | 行为 |
|---|---|
false(默认) | 每次登录新建 token |
true | 同账号(及同设备,若传了 device)复用已有未过期 token,并刷新角色/权限/过期时间 |
典型组合(当前项目):
allowConcurrentLogin: false, // 单点登录感
isShare: false, // 每次登录新 token6. 多端 device
登录时可传 device(如 web / app / pc):
await AuthUtil.login(userId, {
roles,
permissions,
username,
avatar,
device: dto.platform?.trim() || "web",
});影响:
isShare:按同账号 + 同设备复用allowConcurrentLogin: false:只挤掉同设备会话AuthUtil.logoutByDevice(loginId, device):按设备注销AuthUtil.getLoginDevice():取当前设备
7. 会话存储(内存 / Redis)
| 方式 | 条件 | 说明 |
|---|---|---|
内存 MemoryStore | 未传 redis | 进程内 Map;重启丢失;适合开发 |
Redis RedisStore | Auth({ redis }) | 会话与账号-token 集合落 Redis;适合多实例 |
import Redis from "@/core/Redis"; // 项目封装,需含 setJSON/getJSON/sAdd/...
Auth({
redis: Redis,
timeout: 7 * 24 * 60 * 60 * 1000,
});Redis 封装需提供:setJSON、getJSON、del、sAdd、sMembers、sRem,可选 expire。
8. AuthUtil API
登录 / 注销
| 方法 | 说明 |
|---|---|
login(loginId, roles?, permissions?, profile?) | 旧签名:分别传角色、权限、资料 |
login(loginId, options: LoginOptions) | 推荐:对象传 roles / permissions / username / avatar / device / timeout |
logout(token?) | 注销当前(或指定)token |
logoutByLoginId(loginId) | 注销该账号全部会话 |
logoutByDevice(loginId, device) | 按账号 + 设备注销 |
LoginOptions:
{
roles?: string[];
permissions?: string[];
username?: string;
avatar?: string;
device?: string;
timeout?: number; // 覆盖本次会话超时(ms)
}会话与身份
| 方法 | 说明 |
|---|---|
getTokenValue() | 当前请求 token |
getSession() | 当前有效会话;过期则清理并返回 null |
loadSession(token, { touch? }) | 按 token 加载;可控制是否刷新活跃时间 |
isLogin() | 是否已登录 |
checkLogin() | 未登录抛 NotLoginError,返回 loginId |
getLoginId() | 同 checkLogin |
getLoginIdDefaultNull() | 未登录返回 null |
getLoginDevice() | 当前 device |
getRoleList() / getPermissionList() | 角色、权限列表 |
hasRole / hasPermission | 是否拥有 |
checkRole / checkPermission | 校验失败抛 NotRoleError / NotPermissionError |
9. 中间件行为
OPTIONS或命中ignore→ 直接next(),不注入上下文。- 否则解析 token →
loadSession(token, { touch: true })。 - 用
AsyncLocalStorage写入{ req, token, session },再next()。 - 不强制登录;需要登录的接口在业务里调用
AuthUtil.checkLogin()等。
路由上的 @IgnoreAuth() / option.ignoreAuth 仍由 Application 跳过鉴权守卫(与全局 ignore 配合使用)。
10. 错误类型
| 类 | 场景 |
|---|---|
NotLoginError | 未登录 |
NotRoleError | 缺角色 |
NotPermissionError | 缺权限 |
AuthError | 基类 |
由全局异常处理映射为统一响应。
11. 业务示例
// 登录
const token = await AuthUtil.login(userId, {
roles,
permissions,
username: String(user.username ?? ""),
avatar: user.avatar as string | undefined,
device: dto.platform?.trim() || "web",
});
// 注销
await AuthUtil.logout();
// 校验
await AuthUtil.checkLogin();
await AuthUtil.checkPermission("user:edit");前端携带 token(当前配置无 Bearer 前缀):
Authorization: <token>12. 源码位置
| 路径 | 说明 |
|---|---|
src/core/Auth/index.ts | 中间件工厂、matchesIgnore |
src/core/Auth/AuthUtil.ts | 登录/注销/校验、配置合并 |
src/core/Auth/token.ts | Token 风格生成、JWT 签发与校验 |
src/core/Auth/types.ts | AuthOptions / AuthSession / TokenStyle 等 |
src/core/Auth/store.ts | MemoryStore / RedisStore |
src/core/Auth/context.ts | AsyncLocalStorage |
src/core/Auth/errors.ts | 鉴权错误类 |
src/index.ts | 项目实际 Auth 配置 |
与其它模块的关系
- Application:路由守卫调用
AuthUtil.checkLogin/checkPermission(@RequiresLogin/@RequiresPermissions)。 - Log:写操作日志时从会话取
username等。 - 业务 user:
login/logout/checkLogin/getInfo均基于AuthUtil。