CC 咖啡猫的工作空间 Coding Space

Sa-Token 深入原理

轻量级 Java 权限认证框架,核心是登录凭证(Token)管理。开箱即用,API 极简,文档友好。适用场景:不想引入 Spring Security 的复杂性,又需要一个完整的认证授权方案。


1. 核心概念

概念 说明
Token 登录凭证,一次登录生成,后续请求携带
Session 服务端存储的用户会话数据
Token 模式 Token 存在 Redis,验证时不查数据库
注解鉴权 @SaCheckLogin@SaCheckRole 等注解
StpLogic 核心接口,实现登录、验证、踢人等逻辑

2. 工作原理

2.1 登录流程

// 业务层:验证用户名密码后,调用 Sa-Token 登录
@PostMapping("/login")
public Result<?> doLogin(String username, String password) {
    // 1. 业务校验:验证用户名密码是否正确
    User user = userService.checkPassword(username, password);
    if (user == null) {
        return Result.error("用户名或密码错误");
    }

    // 2. 登录:创建 Token,存入 Redis
    StpUtil.login(user.getId());  // 参数是用户唯一标识
    // 默认 Token 名:satoken,值是一串 UUID
    // 存入 Redis:satoken:uuid → userId,过期时间 30 天

    // 3. 返回 Token 给前端
    return Result.ok().put("token", StpUtil.getTokenValue());
}
前端获取 Token 后,后续请求携带:
  Header: satoken: <token值>
  或
  Cookie: satoken=<token值>

2.2 验证流程

请求到达
    ↓
SaTokenFilter(拦截器)读取 satoken
    ↓
Redis 查询 Token 是否存在且未过期
    ↓
Token 有效 → 取出 userId → 创建 Authentication → 继续业务代码
    ↓
Token 无效/过期 → 抛出 NotLoginException → 全局异常处理返回 401

2.3 StpLogic 核心接口

public interface StpLogic {
    // 登录
    void login(Object id);

    // 登出
    void logout();

    // 检查是否已登录
    void checkLogin();

    // 检查角色
    void checkRole(String... roles);

    // 检查权限码
    void checkPermission(String code);

    // 获取当前 userId
    Object getLoginId();

    // 获取 Token 信息
    TokenInfo getTokenInfo();
}

3. 核心功能

3.1 登录与验证

// 登录(返回 Token)
StpUtil.login(userId);

// 验证是否已登录(未登录抛 NotLoginException)
StpUtil.checkLogin();

// 获取当前登录用户 ID
StpUtil.getLoginIdAsLong();

// 获取当前登录用户 ID(通用,返回 Object)
StpUtil.getLoginId();

// 登出
StpUtil.logout();

3.2 角色与权限校验

// 角色校验(只校验角色,不校验权限)
StpUtil.checkRole("admin");

// 角色校验(必须拥有所有指定角色)
StpUtil.checkRole("admin", "user");

// 角色校验(拥有任一角色即可)
StpUtil.checkRoleOr("admin", "super_admin");

// 权限码校验
StpUtil.checkPermission("user:add");

// 权限码校验(拥有任一即可)
StpUtil.checkPermissionOr("user:add", "user:export");

3.3 注解鉴权

// 登录校验:必须登录才能访问
@SaCheckLogin
@GetMapping("/user/info")
public User getUserInfo() { ... }

// 角色校验:必须有 admin 角色
@SaCheckRole("admin")
@DeleteMapping("/user/{id}")
public void deleteUser(@PathVariable Long id) { ... }

// 权限码校验:必须有 user:add 权限
@SaCheckPermission("user:add")
@PostMapping("/user")
public void addUser(@RequestBody User user) { ... }

// 注解组合:同时校验角色和权限
@SaCheckLogin
@SaCheckPermission(value = "user:export", mode = SaMode.OR)
@GetMapping("/user/export")
public void exportUsers() { ... }

3.4 踢人下线

// 让指定用户的 Token 失效(强制下线)
StpUtil.kickout(userId);

// 让指定 Token 失效
StpUtil.kickoutByToken(tokenValue);

// 查询当前在线用户数
StpUtil.getSession().getLoginCount();

4. Token 存储模式

4.1 Redis 存储(默认)

# application.yml
sa-token:
  token-name: satoken
  timeout: 86400              # Token 有效期(秒),默认 30 天
  token-session: -1            # Token-Session 超时(-1 不创建)
  token style: uuid            # Token 生成风格

数据存在 Redis:

Key: satoken:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Value: userId
TTL: 30 天

4.2 JWT 模式(无状态)

不依赖 Redis,服务端不存储 Token,Token 本身包含用户信息:

@Configuration
public class SaTokenConfig {
    @Bean
    public StpLogic jwtStpLogic() {
        return new StpLogicJwtFor通行 Jwt {
            // 返回 JWT 秘钥
            return "xxxx";
        };
    }
}

JWT Token 结构:

Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"loginId": 10086, "loginType": "login", "exp": 1735689600}
Signature: HMAC-SHA256(header + "." + payload, secret)

JWT 优点:不依赖 Redis,可跨服务验证 JWT 缺点:Token 一旦签发无法主动撤销,必须等过期

// 登录时指定 Cookie 写入
StpUtil.login(userId, "SatokenCookie");  // 第二个参数是 Cookie 名

// 获取 Cookie 值
StpUtil.getCookie().getValue();

5. SSO 单点登录

5.1 什么是 SSO

用户在一个系统登录后,访问另一个系统无需重新登录。

用户登录 app1.com → 获取 Token
    ↓
访问 app2.com → 自动带上 app1.com 的 Token
    ↓
app2.com 验证 Token → 发现是跨域 → 向 app1.com 发起校验
    ↓
app1.com 返回:有效,返回用户信息

5.2 Sa-Token SSO 配置

sa-token:
  # SSO 相关配置
  is-sso: true
  sso:
    ticket-name: satoken_ticket     # 票据名称
    ticket-timeout: 86400           # 票据有效期
    allow-share: true               # 是否允许 Ticket 共享

SSO 有三种模式:

  • OAuth2 模式:标准单点登录,通过 Ticket 票据交换
  • Header 模式:前端把 Token 一并带走(简单,不推荐跨域)
  • URL 重定向模式:通过 URL 参数传递 Ticket

6. 与 Spring Security / Shiro 综合对比

6.1 功能维度对比

功能 Sa-Token Spring Security Shiro
登录认证
角色校验
权限码校验
注解鉴权
踢人下线
Token / Session
Redis 共享
SSOO 单点登录 ✅(内置) ❌(需 Spring Security OAuth2) ❌(需扩展)
OAuth2 第三方登录
方法级安全注解
密码加密 ✅(内置多种)
CSRF 防护
细粒度权限(数据权限) ❌(需插件)
与 Spring 耦合度 低(独立使用) 高(强关联)

6.2 学习曲线对比

维度 Sa-Token Spring Security Shiro
文档友好度 ⭐⭐⭐⭐⭐(文档简洁,示例丰富) ⭐⭐⭐(官方文档晦涩) ⭐⭐⭐⭐
上手难度 ⭐⭐(5 分钟跑通) ⭐⭐⭐⭐⭐(学习曲线陡峭) ⭐⭐⭐
配置复杂度 ⭐⭐(几行配置) ⭐⭐⭐⭐⭐(配置项极多) ⭐⭐⭐

6.3 适用场景对比

场景 推荐 原因
快速中小型项目 Sa-Token 配置少,上手快
Spring 全家桶 + 复杂权限 Spring Security 生态完整,集成度高
非 Spring 项目 Shiro 不强依赖 Spring
需要 SSO Sa-Token(简单 SSO) 内置 SSO,配置简单
需要 OAuth2 第三方登录 Spring Security 原生支持 OAuth2 Client
需要微服务认证 Sa-Token(JWT)或 Spring Security 都支持 JWT
需要细粒度数据权限 目前都不满足 需自研或用 MyBatis-Plus 插件

6.4 性能与轻量对比

维度 Sa-Token Spring Security Shiro
依赖包大小 ~200KB(极轻量) ~5MB(庞大) ~1MB
启动时间影响 几乎无 显著 较小
内存占用
Redis 操作 每次验证 1 次 可配置 可配置

6.5 生态与维护

维度 Sa-Token Spring Security Shiro
开源维护 活跃(国内开源) 非常活跃(VMware) 较活跃(Apache)
社区规模 中等 庞大 中等
最后更新时间 持续更新 持续更新 2024 年仍有更新
资料丰富度 中文资料多 中英文资料丰富 中文资料多

6.6 选型建议

项目需求判断树:

需要 OAuth2 / 第三方登录?
    └── 是 → Spring Security

需要极简配置 / 快速上线?
    └── 是 → Sa-Token

非 Spring 项目?
    └── 是 → Shiro

需要 SSO 单点登录?
    └── 是 → Sa-Token(简单场景)/ Spring Security OAuth2(复杂场景)

Spring 全家桶 + 复杂安全场景?
    └── 是 → Spring Security(即使配置复杂,但收益高)

一般业务系统,认证授权不复杂?
    └── Sa-Token(性价比最高)

7. 常见问题

7.1 Token 过期后如何处理

// 全局异常处理
@ExceptionHandler(NotLoginException.class)
public Result<?> handleNotLogin(NotLoginException e) {
    return Result.error(401, "未登录或登录已过期");
}

7.2 如何自定义 Token 存储

// 实现 StpLogic 接口,替换默认实现
@Configuration
public class SaTokenConfig {
    @Bean
    public StpLogic myStpLogic() {
        return new StpLogicSimpleExt("my-token") {
            // 自定义登录逻辑
            @Override
            public void login(Object id, LoginModel model) {
                // 自定义存储到数据库
            }
        };
    }
}

7.3 前后端分离如何传递 Token

// 前端:放在 Header 中
axios.defaults.headers.common['satoken'] = localStorage.getItem('satoken');

// 或放在 Cookie 中(自动携带)
document.cookie = "satoken=" + token;

7.4 Sa-Token 无法替代 Spring Security 的场景

  • 需要细粒度行级数据权限控制
  • 需要 OAuth2 资源服务器认证
  • 需要与 Spring Security OAuth2 配合的第三方登录(微信、GitHub)
  • 复杂的企业级安全管理(多租户、敏感操作审计)