Spring Boot整合Sa-Token实现权限认证
一文搞定Spring Boot权限认证:登录、权限、踢人、JWT、签名校验全覆盖
前言
Sa-Token 是一个轻量级、功能强大的 Java 权限认证框架,与 Spring Boot 完美集成,主打登录认证、权限验证、Session会话、单点登录、OAuth2.0、微服务网关鉴权等功能。该框架具有零配置启动、API极简、高扩展性等特点,能够以简单优雅的方式解决系统权限认证问题。
Sa-Token 目前最新版本为 v1.44.0,GitHub 关注量已超过 18k+ Star,是主要竞争框架 Spring Security 的 1.97 倍,Apache Shiro 的 4.18 倍,曾获 GVP - Gitee 最有价值开源项目、OSCHINA 2021 人气指数 TOP 30 开源项目等多项荣誉。
一、Spring Boot项目快速整合
1.1 创建Spring Boot项目
使用 Spring Initializr 创建项目,或在现有 Spring Boot 项目中添加依赖。
项目结构示例:
src/main/java
├── com/example/satoken
│ ├── SaTokenApplication.java # 启动类
│ ├── config
│ │ ├── SaTokenConfigure.java # Sa-Token配置类
│ │ └── StpInterfaceImpl.java # 权限接口实现
│ ├── controller
│ │ ├── LoginController.java # 登录控制器
│ │ ├── UserController.java # 用户控制器
│ │ ├── AdminController.java # 管理员控制器
│ │ └── ApiController.java # API接口控制器
│ └── model
│ └── User.java # 用户实体类
src/main/resources
└── application.yml # 配置文件
1.2 添加 Maven 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>sa-token-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
<sa-token.version>1.44.0</sa-token.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token 核心依赖 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 整合 Redis(使用jackson序列化) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Redis 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2 创建Spring Boot启动类
package com.example.satoken;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Sa-Token Spring Boot 启动类
*/
@SpringBootApplication
public class SaTokenApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenApplication.class, args);
System.out.println("Sa-Token Spring Boot项目启动成功!");
}
}
1.3 基础配置 (application.yml)
server:
port: 8080
servlet:
context-path: /api
spring:
application:
name: sa-token-demo
# Redis配置
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 3000ms
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
# Sa-Token 配置
sa-token:
# token 名称(默认 satoken)
token-name: Authorization
# token 有效期(秒),默认 7 天
timeout: 2592000
# token 最低活跃时间(秒),默认 0(不刷新)
active-timeout: 1800
# 是否允许同一账号多地登录(true=允许,false=踢掉前一个)
is-concurrent: true
# 是否启用二级认证(如短信验证码)
is-share: false
# token 风格(默认为:uuid)
token-style: uuid
# 是否输出日志
is-log: true
💡 小贴士:初学者可以先不配,全部用默认值!
⚠️ 重要:只要引入 sa-token-redis-jackson 依赖并配置Redis,Sa-Token 会自动使用 Redis!
1.4 实现登录接口
package com.example.satoken.controller;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 登录控制器
*/
@RestController
@RequestMapping("/auth")
public class LoginController {
// 模拟数据库用户(实际应查数据库)
private static final Map<String, String> USER_DB = new HashMap<>();
static {
USER_DB.put("admin", "123456");
USER_DB.put("user", "123456");
}
/**
* 用户登录
*/
@PostMapping("/login")
public Map<String, Object> login(@RequestParam String username, @RequestParam String password) {
Map<String, Object> result = new HashMap<>();
// 1. 校验账号密码
if (!USER_DB.containsKey(username) || !USER_DB.get(username).equals(password)) {
result.put("code", 401);
result.put("msg", "账号或密码错误");
return result;
}
// 2. 写入当前登录用户 ID(Sa-Token 自动创建 Token 并绑定会话)
Long userId = "admin".equals(username) ? 10001L : 10002L;
StpUtil.login(userId);
// 3. 返回Token和用户信息
result.put("code", 200);
result.put("msg", "登录成功");
result.put("token", StpUtil.getTokenValue());
result.put("tokenName", StpUtil.getTokenName());
result.put("userId", userId);
result.put("username", username);
return result;
}
/**
* 用户退出
*/
@PostMapping("/logout")
public Map<String, Object> logout() {
StpUtil.logout();
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("msg", "已退出登录");
return result;
}
/**
* 检查是否登录
*/
@GetMapping("/isLogin")
public Map<String, Object> isLogin() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("isLogin", StpUtil.isLogin());
return result;
}
/**
* 获取当前登录用户信息
*/
@GetMapping("/userInfo")
public Map<String, Object> getUserInfo() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("msg", "获取成功");
result.put("userId", StpUtil.getLoginId());
result.put("tokenValue", StpUtil.getTokenValue());
result.put("loginDevice", StpUtil.getLoginDevice());
return result;
}
}
💡 核心API:调用
StpUtil.login(userId)后,Sa-Token 会自动生成 Token 并关联用户会话!
1.5 配置Sa-Token拦截器
package com.example.satoken.config;
import cn.dev33.satoken.interceptor.SaRouteInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Sa-Token 配置类
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册 Sa-Token 拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaRouteInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(
"/auth/login", // 登录接口
"/auth/logout", // 退出接口
"/auth/isLogin", // 检查登录状态
"/error", // 错误页面
"/doc.html", // Swagger文档
"/webjars/**", // Swagger静态资源
"/swagger-resources/**" // Swagger资源
);
}
}
这样,所有未登录的请求访问受保护接口都会返回401!
二、登录认证深度实践
2.1 单端登录
单端登录指同一账号在一个设备登录后,其他设备登录会踢掉前一个登录。
sa-token:
# 是否允许同一账号多地登录(false=新登录踢掉旧会话)
is-concurrent: false
实现代码示例:
package com.example.satoken.controller;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 单端登录控制器
*/
@RestController
@RequestMapping("/login/single")
public class SingleLoginController {
private static final Map<String, String> USER_DB = new HashMap<>();
static {
USER_DB.put("admin", "123456");
}
/**
* 单端登录(同一账号只能在一个设备登录)
*/
@PostMapping("/login")
public Map<String, Object> login(@RequestParam String username, @RequestParam String password,
@RequestParam(defaultValue = "PC") String device) {
Map<String, Object> result = new HashMap<>();
if (!USER_DB.containsKey(username) || !USER_DB.get(username).equals(password)) {
result.put("code", 401);
result.put("msg", "账号或密码错误");
return result;
}
// 单端登录:新登录会踢掉旧会话
StpUtil.login(10001L, device);
result.put("code", 200);
result.put("msg", "登录成功,其他设备已下线");
result.put("token", StpUtil.getTokenValue());
result.put("device", device);
return result;
}
}
💡 应用场景:大多数后台管理系统应设为 false,提升安全性!
2.2 多端登录
多端登录允许同一账号在多个不同类型设备同时登录,如 PC 端和手机端可以同时在线。
sa-token:
# 允许多地同时登录
is-concurrent: true
# 每个登录创建独立Token
is-share: false
实现代码示例:
package com.example.satoken.controller;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 多端登录控制器
*/
@RestController
@RequestMapping("/login/multi")
public class MultiLoginController {
private static final Map<String, String> USER_DB = new HashMap<>();
static {
USER_DB.put("admin", "123456");
}
/**
* 多端登录(同一账号可在不同设备同时登录)
*/
@PostMapping("/login")
public Map<String, Object> login(@RequestParam String username, @RequestParam String password,
@RequestParam(defaultValue = "PC") String device) {
Map<String, Object> result = new HashMap<>();
if (!USER_DB.containsKey(username) || !USER_DB.get(username).equals(password)) {
result.put("code", 401);
result.put("msg", "账号或密码错误");
return result;
}
// 多端登录:不同设备可以同时在线
StpUtil.login(10001L, device);
result.put("code", 200);
result.put("msg", "登录成功");
result.put("token", StpUtil.getTokenValue());
result.put("device", device);
return result;
}
/**
* 获取当前账号的所有在线会话
*/
@GetMapping("/sessions")
public Map<String, Object> getSessions() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("msg", "获取成功");
result.put("sessions", StpUtil.getTokenSessionListByLoginId(10001L));
return result;
}
}
2.3 同端互斥登录
同端互斥登录允许同一账号在不同类型设备同时登录,但在同一类型设备下只能单端登录,如两个手机不能同时登录,但手机和电脑可以同时登录。
sa-token:
is-concurrent: false
replaced-range: CURR_DEVICE_TYPE # 仅同设备类型互斥
2.4 七天内免登录
通过设置合理的 timeout 和 active-timeout 配置实现免登录功能:
sa-token:
# Token有效期设为7天
timeout: 604800
# 30分钟内无操作则过期
active-timeout: 1800
💡 推荐配置:
- 普通系统:timeout=86400(1天),active-timeout=1800(30分钟)
- 金融/支付系统:timeout=1800,active-timeout=300
三、权限认证体系
3.1 实现权限接口
首先实现 StpInterface 接口,提供权限和角色信息:
@Component
public class StpInterfaceImpl implements StpInterface {
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 根据loginId查询用户权限,实际项目中从数据库获取
if (loginId.equals(10001L)) {
return Arrays.asList("user:add", "user:delete", "user:update");
}
return Arrays.asList("user:query");
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 根据loginId查询用户角色,实际项目中从数据库获取
if (loginId.equals(10001L)) {
return Arrays.asList("admin");
}
return Arrays.asList("user");
}
}
3.2 代码方式鉴权
@RestController
public class UserController {
@GetMapping("/user/info")
public Object getUserInfo() {
// 检查是否登录,未登录会抛出NotLoginException异常
StpUtil.checkLogin();
// 获取当前登录用户 ID
Long userId = StpUtil.getLoginIdAsLong();
// 检查是否具有admin角色
if (StpUtil.hasRole("admin")) {
// 管理员操作
}
// 检查是否具有user:delete权限
if (StpUtil.hasPermission("user:delete")) {
// 有权限操作
}
return Map.of("userId", userId, "username", "admin");
}
}
3.3 注解式鉴权(推荐)
首先注册注解拦截器:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
}
然后使用注解进行权限控制:
@RestController
public class UserController {
// 必须登录才能访问
@SaCheckLogin
@GetMapping("/user/info")
public Object getUserInfo() {
Long userId = StpUtil.getLoginIdAsLong();
return Map.of("userId", userId, "username", "admin");
}
// 必须具有 admin 角色
@SaCheckRole("admin")
@GetMapping("/admin/setting")
public String adminSetting() {
return "管理员设置页面";
}
// 必须具有 user:delete 权限
@SaCheckPermission("user:delete")
@GetMapping("/user/delete")
public String deleteUser() {
return "删除用户成功";
}
// 必须具有多个权限(AND关系)
@SaCheckPermission(value = {"user:add", "user:edit"}, mode = SaMode.AND)
@PostMapping("/user/saveOrUpdate")
public String saveOrUpdate() {
return "保存或更新用户成功";
}
// 具有任一权限即可(OR关系)
@SaCheckPermission(value = {"user:view", "user:list"}, mode = SaMode.OR)
@GetMapping("/user/list")
public String list() {
return "用户列表";
}
}
3.4 会话二级认证
二级认证适用于敏感操作,如支付、修改密码等场景:
@RestController
public class PayController {
// 发起二级认证
@GetMapping("/pay/openSafe")
public String openSafe() {
// 开启二级认证,有效期5分钟
StpUtil.openSafe(300);
return "二级认证已开启";
}
// 需要二级认证的敏感操作
@SaCheckSafe
@PostMapping("/pay/transfer")
public String transferMoney() {
return "转账成功";
}
}
四、踢人下线功能
4.1 根据账号ID踢人下线
@RestController
@RequestMapping("/admin")
public class AdminController {
// 将指定账号踢下线
@PostMapping("/kickout")
public String kickout(@RequestParam Long userId) {
StpUtil.kickout(userId);
return "用户已被踢下线";
}
// 将指定账号的指定端踢下线
@PostMapping("/kickoutByDevice")
public String kickoutByDevice(@RequestParam Long userId, @RequestParam String device) {
StpUtil.kickout(userId, device);
return "用户" + userId + "的" + device + "端已被踢下线";
}
}
4.2 根据Token值踢人下线
@PostMapping("/kickoutByToken")
public String kickoutByToken(@RequestParam String token) {
StpUtil.kickoutByTokenValue(token);
return "Token对应的用户已被踢下线";
}
4.3 强制注销与踢人下线的区别
| 功能 | API | 说明 |
|---|---|---|
| 强制注销 | StpUtil.logout(10001) |
等价于对方主动调用了注销方法,再次访问会提示:Token无效 |
| 踢人下线 | StpUtil.kickout(10001) |
不会清除Token信息,而是将其打上特定标记,再次访问会提示:Token已被踢下线 |
五、前后端分离支持
对于APP、小程序等不支持Cookie的终端,Sa-Token提供了完善的支持。
5.1 前端携带Token
前端需要在请求头中携带Token,默认Header名为satoken(可配置):
// 前端请求示例
axios.get("/user/info", {
headers: {
"Authorization": "xxx-xxx-xxx" // 如果token-name改为Authorization
}
});
5.2 配置Token名称
sa-token:
# token 名称(前端请求头中携带的字段名)
token-name: Authorization
# 不从Cookie读取Token
is-read-cookie: false
⚠️ 安全建议:不要在URL传Token,容易泄露;敏感操作建议加二次验证(如支付前输密码)
六、Token风格定制
6.1 内置Token风格
Sa-Token内置六种Token风格:
sa-token:
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tiktok-uuid)
token-style: uuid
💡 生产推荐:tiktok-uuid,既保证唯一性,又便于日志追踪和数据库索引优化。
6.2 自定义Token生成策略
实现TokenGenerator接口:
@Component
public class MyTokenGenerator implements TokenGenerator {
@Override
public String generate(Object loginId, String loginType) {
// 自定义Token生成逻辑
return UUID.randomUUID().toString().replace("-", "") + System.currentTimeMillis();
}
}
七、JWT集成实战
7.1 添加JWT依赖
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>1.44.0</version>
</dependency>
7.2 配置JWT密钥
sa-token:
# jwt秘钥
jwt-secret-key: your-jwt-secret-key
7.3 注册JWT逻辑实现
@Configuration
public class SaTokenConfig {
@Bean
public StpLogic getStpLogicJwt() {
// Sa-Token 整合 jwt (简单模式)
return new StpLogicJwtForSimple();
}
}
7.4 JWT模式登录
@PostMapping("/jwt/login")
public String jwtLogin(@RequestParam String username, @RequestParam String password) {
// 校验账号密码...
// JWT模式登录
StpUtil.login(10001);
return "JWT登录成功,Token: " + StpUtil.getTokenValue();
}
Sa-Token提供三种模式的JWT集成方案,支持Token扩展参数能力。
八、参数签名安全
Sa-Token提供跨系统API调用签名校验模块,防参数篡改,防请求重放。
8.1 配置签名密钥
sa-token:
sign:
# API 接口签名秘钥
secret-key: your-sign-secret-key
8.2 请求发起端生成签名
@GetMapping("/sendRequest")
public String sendRequest() {
Map<String, Object> params = new HashMap<>();
params.put("userId", 10001);
params.put("money", 100);
// 添加签名参数并拼接成参数字符串
String paramStr = SaSignUtil.addSignParamsAndJoin(params);
// 发送请求
String url = "http://localhost:8080/api/testSign";
return HttpUtil.post(url, paramStr);
}
8.3 请求接收端校验签名
@RestController
@RequestMapping("/api")
public class ApiController {
@PostMapping("/testSign")
public String testSign() {
// 校验请求签名
SaSignUtil.checkRequest(SaHolder.getRequest());
// 处理业务逻辑
return "签名校验通过";
}
// 使用注解方式校验签名
@SaCheckSign
@PostMapping("/testSignByAnnotation")
public String testSignByAnnotation() {
// 处理业务逻辑
return "注解式签名校验通过";
}
}
九、自动续签机制
Sa-Token提供两种Token过期策略,灵活搭配使用,还可自动续签。
9.1 配置自动续签
sa-token:
# token 有效期(秒)
timeout: 86400 # 1天
# token 最低活跃时间(秒)
active-timeout: 1800 # 30分钟无操作则过期
9.2 自动续签原理
- timeout:Token的长久有效期,例如配置为86400(1天),代表1天后Token必定过期
- active-timeout:最低活跃频率,例如配置为1800(30分钟),代表用户如果30分钟无操作,则Token过期
当用户在active-timeout时间内有操作,Token会自动刷新有效期,避免用户频繁登录。
9.3 手动续签
// 检查当前Token是否已经被冻结
StpUtil.checkActiveTimeout();
// 续签当前Token:将[最后操作时间]更新为当前时间戳
StpUtil.updateLastActiveToNow();
// 为指定Token续签
StpUtil.stpLogic.updateLastActiveToNow(tokenValue);
十、生产环境最佳实践
10.1 推荐配置
sa-token:
token-name: Authorization
timeout: 86400 # 1天
active-timeout: 1800 # 30分钟无操作则过期
is-concurrent: false # 单点登录
is-share: false
token-style: tiktok-uuid
is-log: false # 生产关闭日志
is-read-cookie: false # 前后端分离不读取Cookie
10.2 安全建议
- 生产环境务必使用Redis,避免服务重启丢失会话
- 敏感操作建议加二次验证(
StpUtil.openSafe()) - 不要在URL中传递Token,使用请求头
- 合理设置Token超时时间,平衡安全性和用户体验
- 定期轮换JWT密钥和签名密钥
10.3 常用API速查表
| 功能 | 代码示例 |
|---|---|
| 用户登录 | StpUtil.login(10001); |
| 获取当前登录用户ID | StpUtil.getLoginId(); |
| 检查是否登录 | StpUtil.isLogin(); |
| 注销登录 | StpUtil.logout(); |
| 踢人下线 | StpUtil.kickout(10001); |
| 检查角色 | StpUtil.hasRole("admin"); |
| 检查权限 | StpUtil.hasPermission("user:add"); |
| 获取Session | StpUtil.getSession(); |
| 指定设备登录 | StpUtil.login(10001, "PC"); |
| 指定设备注销 | StpUtil.logoutByLoginId(10001, "PC"); |
| 临时身份切换 | StpUtil.switchTo(10044); |
十一、总结
通过以上配置和代码示例,你可以基于Sa-Token实现完整的权限认证系统,包括登录认证、权限管理、会话控制等核心功能。Sa-Token的API设计简洁直观,极大降低了权限系统的开发难度,让开发者可以更专注于业务逻辑实现。
核心优势总结:
- ✅ API极简:一行代码搞定登录、鉴权
- ✅ 功能全面:覆盖登录、权限、会话、单点登录、OAuth2.0等
- ✅ 易于集成:Spring Boot零配置启动
- ✅ 扩展性强:支持自定义Token、JWT、签名校验等
- ✅ 社区活跃:18k+ Star,持续更新维护
🎯 推荐理由:相比Spring Security的复杂性,Sa-Token提供了更直观的API设计;相比Shiro的轻量级,Sa-Token功能更加丰富完善。如果你正在寻找一款简单易用、功能强大的Java权限认证框架,Sa-Token绝对是首选!
参考文档:
更多推荐



所有评论(0)