Skip to content

Auth 鉴权(类 Sa-Token)

core/Auth 提供会话式登录鉴权:中间件只负责解析 token、注入请求上下文,不强制登录;业务侧用 AuthUtil 做登录、注销、校验角色/权限。设计对齐 Sa-Token 常见配置项。

目录

  1. 注册与配置
  2. Token 读取顺序
  3. Token 风格
  4. 会话与超时
  5. 并发登录与共享 Token
  6. 多端 device
  7. 会话存储(内存 / Redis)
  8. AuthUtil API
  9. 中间件行为
  10. 错误类型
  11. 业务示例
  12. 源码位置

1. 注册与配置

src/index.ts 中注册:

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 字段一览

字段类型默认说明
tokenNamestring"satoken"Token 名称(请求头 / Cookie 名)
headerstring"Authorization"兼容旧配置的备用请求头
tokenPrefixstring"Bearer"前缀;设为 "" 表示无前缀(项目默认)
timeoutnumber2h(ms)绝对有效期;-1 永不过期
activityTimeoutnumber-1闲置超时(ms);-1 不启用
allowConcurrentLoginbooleantrue是否允许多端同时在线
isSharebooleanfalse同账号是否复用已有有效 token
tokenStyleTokenStyle"uuid"Token 生成风格
createToken(payload) => string | Promise<string>自定义生成(优先于 tokenStyle
jwtSecretstring内置默认串tokenStyle=jwt 时使用
jwtAlgorithmstring"HS256"JWT 算法
ignorestring[][]不注入鉴权上下文的路径(支持尾部 *
redisRedis 封装传入则会话走 Redis
readCookiebooleantrue是否从 Cookie 读 token

另有内置忽略:/.well-known/favicon.ico/robots.txt/sitemap.xml


2. Token 读取顺序

AuthUtil.readTokenFromRequest(req) 按以下顺序解析:

  1. tokenName 请求头(如 Authorization: xxx
  2. header 请求头(若与 tokenName 同名则跳过,避免重复读取)
  3. Cookie[tokenName]readCookie !== false 时)

若配置了 tokenPrefix(如 Bearer),会自动去掉前缀再取 token。当前项目 tokenPrefix: "",前端可直接传裸 token。


3. Token 风格

类型 TokenStyle

说明
uuid标准 UUID(带横线)
simple-uuid去掉横线的 UUID
random-3232 位 hex
random-6464 位 hex
random-128128 位 hex
tik短可读风格,如 a1b2c3_d4e5f6_789abc
jwt使用 jsonwebtoken 签发(需 jwtSecret

优先级:createToken > jwt > 其它 tokenStyle

JWT 校验:加载会话前会 jwt.verify;非法则清理并视为未登录。非 jwt 风格不校验签名,以会话存储为准。

ts
Auth({
  tokenStyle: "jwt",
  jwtSecret: process.env.JWT_SECRET || "change-me",
  jwtAlgorithm: "HS256",
});

自定义:

ts
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 + timeouttimeout < 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,并刷新角色/权限/过期时间

典型组合(当前项目):

ts
allowConcurrentLogin: false, // 单点登录感
isShare: false,              // 每次登录新 token

6. 多端 device

登录时可传 device(如 web / app / pc):

ts
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 RedisStoreAuth({ redis })会话与账号-token 集合落 Redis;适合多实例
ts
import Redis from "@/core/Redis"; // 项目封装,需含 setJSON/getJSON/sAdd/...

Auth({
  redis: Redis,
  timeout: 7 * 24 * 60 * 60 * 1000,
});

Redis 封装需提供:setJSONgetJSONdelsAddsMemberssRem,可选 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

ts
{
  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. 中间件行为

  1. OPTIONS 或命中 ignore → 直接 next(),不注入上下文。
  2. 否则解析 token → loadSession(token, { touch: true })
  3. AsyncLocalStorage 写入 { req, token, session },再 next()
  4. 不强制登录;需要登录的接口在业务里调用 AuthUtil.checkLogin() 等。

路由上的 @IgnoreAuth() / option.ignoreAuth 仍由 Application 跳过鉴权守卫(与全局 ignore 配合使用)。


10. 错误类型

场景
NotLoginError未登录
NotRoleError缺角色
NotPermissionError缺权限
AuthError基类

由全局异常处理映射为统一响应。


11. 业务示例

ts
// 登录
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 前缀):

http
Authorization: <token>

12. 源码位置

路径说明
src/core/Auth/index.ts中间件工厂、matchesIgnore
src/core/Auth/AuthUtil.ts登录/注销/校验、配置合并
src/core/Auth/token.tsToken 风格生成、JWT 签发与校验
src/core/Auth/types.tsAuthOptions / AuthSession / TokenStyle
src/core/Auth/store.tsMemoryStore / RedisStore
src/core/Auth/context.tsAsyncLocalStorage
src/core/Auth/errors.ts鉴权错误类
src/index.ts项目实际 Auth 配置

与其它模块的关系

  • Application:路由守卫调用 AuthUtil.checkLogin / checkPermission@RequiresLogin / @RequiresPermissions)。
  • Log:写操作日志时从会话取 username 等。
  • 业务 userlogin / logout / checkLogin / getInfo 均基于 AuthUtil

基于 VitePress 构建