Satoken-权限认证
Sa-Token 的整体流程可以理解为:登录时建立“Token 与用户”的关系;请求时根据 Token 找到用户;需要权限校验时,把用户 ID 传给你实现的权限查询方法,再检查权限。
下面用“企业管理员登录后验收子任务”串起来讲。
1.先分清几个核心概念
| 概念 | 示例 | 作用 |
|---|---|---|
用户 ID,即 loginId |
1001 |
标识用户是谁 |
| Token | abc123 |
客户端携带的登录凭证 |
| 登录态 | abc123 → 用户1001,以及有效期等信息 |
服务端据此识别用户是否已登录 |
SaSession |
保存用户昵称、企业 ID 等数据 | 服务端的数据存储空间 |
| 角色 | enterprise-admin |
表达用户的身份分类 |
| 权限 | work-order:accept |
表达允许执行的具体操作 |
StpUtil |
login()、checkPermission() |
业务代码调用 Sa-Token 的主要入口 |
StpInterface |
你编写的接口实现类 | 向框架提供用户的角色、权限集合 |
2.登录:Token 是怎么生成的?
用户登录时,先由你的业务代码验证账号密码,验证通过后调用:
StpUtil.login(user.getId());
这句话的含义是:
这个用户已经通过身份验证,请为他建立登录态。
示例:
@PostMapping("/login")
public SaTokenInfo login(@RequestBody LoginRequest request) {
// 1. 业务代码验证账号密码
User user = userService.checkPassword(
request.getUsername(),
request.getPassword()
);
// 2. 建立登录态
StpUtil.login(user.getId());
// 3. 返回Token等信息
return StpUtil.getTokenInfo();
}
假设用户 ID 为 1001,Sa-Token 生成的 Token 为 abc123,服务端保存的关系可以简化理解为:
Token:abc123
对应用户:1001
有效期:……
实际是否生成新 Token、是否复用已有 Token,受登录配置影响。
账号密码验证由你负责,生成和管理登录凭证由 Sa-Token 负责。 默认 Token 通常是随机字符串,不一定是 JWT。
3.请求:Token 怎么变成用户 ID?
客户端取得 Token 后,后续请求携带它。例如使用请求头:
POST /work-orders/2001/accept
satoken: abc123
Token 名称和读取位置可以配置,也可以通过 Cookie 携带。
当代码调用:
StpUtil.getLoginId();
框架就会通过当前请求上下文获取 Token,并查询对应的登录态:
当前请求
↓
读取 Token:abc123
↓
查询登录态,检查有效性
↓
获得 loginId:1001
因此:
StpUtil.checkLogin(); // 检查是否已登录
Long userId = StpUtil.getLoginIdAsLong(); // 获取用户ID
不要求你给这两个方法传 Token,因为框架已经接入了 Web 请求上下文,能读取当前请求。
不同请求通过各自的请求上下文识别用户,不是把所有人的用户 ID 存进一个公共静态变量。 StpUtil 虽然是静态工具类,但它调用的逻辑会结合当前请求执行。
这些登录态数据由 Sa-Token 的存储层管理。默认可以使用内存,也可以接入 Redis;多实例部署时通常使用共享存储。
4.权限:用户 ID 怎么传给 getPermissionList()?
你先提供一个接口实现:
@Component
@RequiredArgsConstructor
public class StpInterfaceImpl implements StpInterface {
private final PermissionService permissionService;
private final RoleService roleService;
@Override
public List<String> getPermissionList(
Object loginId, String loginType) {
Long userId = Long.valueOf(loginId.toString());
return permissionService.listPermissionCodes(userId);
}
@Override
public List<String> getRoleList(
Object loginId, String loginType) {
Long userId = Long.valueOf(loginId.toString());
return roleService.listRoleCodes(userId);
}
}
在常见的 Spring Boot 集成中:
@Component 注册你的实现类
↓
Sa-Token 自动配置接入这个实现
↓
框架需要权限集合时,调用你的方法
接着,业务代码执行:
StpUtil.checkPermission("work-order:accept");
框架的处理过程是:
识别当前请求的 Token
↓
获取 loginId = 1001
↓
调用你实现的:
getPermissionList(1001, "login")
↓
你的代码查询用户1001的权限
↓
返回 ["work-order:view", "work-order:accept"]
↓
框架匹配 work-order:accept
↓
匹配成功,继续执行
内部逻辑可以用下面的示意代码理解,它不是逐行对应的源码:
public void checkPermission(String permission) {
// 框架获取当前登录用户
Object loginId = getLoginId();
// 框架获取你提供的实现
StpInterface impl = SaManager.getStpInterface();
// 框架主动把用户ID传给你的方法
List<String> permissions =
impl.getPermissionList(loginId, getLoginType());
// 按权限匹配策略检查,默认支持通配符
// 没有权限则抛出 NotPermissionException
}
这里的关键就是接口回调:
你负责实现“怎么查询权限”,框架负责决定“什么时候调用”,并传入所需参数。
loginType 表示账号体系,例如默认的 "login"。它不是角色,也不是“密码登录、短信登录”这种登录方式。
5.用户、角色、权限之间怎么关联?
常见的数据库设计是:
用户1001
↓ 用户角色表
企业管理员角色
↓ 角色权限表
work-order:view
work-order:accept
你的权限服务负责根据用户 ID,把这条关系查询出来:
permissionService.listPermissionCodes(1001L);
Sa-Token 不知道你的表叫什么、怎么关联,它只需要最终结果:
List.of(
"work-order:view",
"work-order:accept"
);
这几个方法的职责不同:
| 方法 | 检查内容 | 通常需要的业务接口 |
|---|---|---|
checkLogin() |
是否已登录 | 不需要查询角色或权限集合 |
checkPermission("work-order:accept") |
是否具有某项权限 | getPermissionList() |
checkRole("enterprise-admin") |
是否具有某个角色 | getRoleList() |
checkPermission() 不会自动调用 getRoleList(),再替你去数据库关联角色权限。 如果用户通过角色获得权限,这个查询过程需要在你的权限服务中实现。
权限集合是否查数据库、是否走缓存,也取决于你的实现。框架知道登录用户是谁,不代表已经自动缓存了这个用户的全部权限。
6.Session 放在哪里,和这些流程有什么关系?
Token 用于识别登录身份,Session 用于在服务端保存相关数据:
客户端:
Token = abc123
服务端登录态:
abc123 → 用户1001
用户1001的账号Session:
nickname = 张三
enterpriseId = 88
例如:
// 获取当前账号的Session,保存企业ID
StpUtil.getSession().set("enterpriseId", 88L);
// 后续请求中取出
Long enterpriseId =
(Long) StpUtil.getSession().get("enterpriseId");
执行 getSession() 时,框架先识别当前用户,再定位这个账号的 Session。
需要区分:
- 账号 Session:按用户账号划分,
StpUtil.getSession()获取这一类。 - Token Session:按 Token 划分,可以通过
getTokenSession()获取,适合保存与某个登录凭证关联的数据。
账号的多个 Token 可以对应同一个账号 Session。Session 按需获取、创建,不能简单理解成每次登录必然新建一个账号 Session。
此外,Sa-Token 的 SaSession 和 Servlet 的 HttpSession 是不同机制。
7.放回你的订单验收项目,完整请求是怎样的?
@PostMapping("/{id}/accept")
public void accept(@PathVariable Long id) {
// 1. 检查当前登录用户有没有验收权限
StpUtil.checkPermission("work-order:accept");
// 2. 获取当前用户ID
Long userId = StpUtil.getLoginIdAsLong();
// 3. 执行业务
workOrderService.accept(id, userId);
}
完整链路是:
客户端携带 Token 请求验收
↓
Sa-Token 识别当前用户1001
↓
调用 getPermissionList(1001, "login")
↓
你的权限服务查询并返回权限集合
↓
Sa-Token 判断拥有 work-order:accept
↓
进入 Service
↓
检查订单是否属于用户所在企业
↓
检查订单状态和验收条件
↓
执行事务和条件更新
这里有三层不同的检查:
| 层次 | 回答的问题 | 实现位置 |
|---|---|---|
| 登录认证 | 你是谁,是否已登录? | Sa-Token 登录态校验 |
| 操作权限 | 你能不能使用验收功能? | Sa-Token 权限校验 + 你提供的权限集合 |
| 业务校验 | 你能不能验收这一张订单? | Service 中的数据归属、状态等检查 |
面试时可以这样说:
我们先由业务代码验证账号密码,成功后调用
StpUtil.login(userId)建立登录态并返回 Token。后续请求携带 Token,Sa-Token 据此识别当前用户。执行权限检查时,框架会把用户 ID 传给我们实现的StpInterface,获取权限集合并匹配所需权限。权限通过后,Service 还会检查订单归属、状态等业务条件。

浙公网安备 33010602011771号