跳到文章内容
技术手记

xz 的技术手记

把问题想清楚,
把过程记下来。

关于代码、实践,以及一点一滴的理解。

开始阅读

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 还会检查订单归属、状态等业务条件。

posted @ 2026-11-07 21:44  xz_move_on  阅读(9)  评论(0)    收藏  举报