一、Sa-Token框架概述
1.1 什么是Sa-Token
Sa-Token是一个轻量级Java权限认证框架,主要解决登录认证、权限认证、单点登录(SSO)、OAuth2.0、分布式Session会话、微服务网关鉴权等一系列权限相关问题。它以简单、强大、优雅为设计理念,让权限认证变得简单而不失灵活。
核心优势:
- 简洁易用:API设计简洁明了,学习成本低,几行代码即可实现完整登录鉴权功能
- 功能全面:支持多种登录模式、权限校验、会话管理、踢人下线等丰富功能
- 扩展性强:支持插件化扩展,可集成Redis、自定义存储等
- 生态完善:国内开源社区活跃,文档全中文,曾多次位列Gitee最受欢迎开源项目
1.2 与Spring Security对比
| 维度 | Sa-Token | Spring Security |
|---|---|---|
| 学习成本 | 1天能干活 | 1周能看懂文档 |
| 配置方式 | 注解+简单配置 | XML/Java配置+复杂继承体系 |
| 代码量 | 10行代码实现登录 | 50行起步+各种类继承 |
| 社区资源 | 中文文档友好 | 英文文档+零散博客 |
| 适用场景 | 中小项目快速开发 | 企业级复杂权限场景 |
Sa-Token更适合希望快速搭建安全、功能完善的权限系统,同时避免繁琐配置的场景。
二、SpringBoot集成Sa-Token
2.1 引入依赖
根据SpringBoot版本选择对应的starter:
<!-- Spring Boot 2.x -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.36.0</version>
</dependency>
<!-- Spring Boot 3.x -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.36.0</version>
</dependency>
2.2 基础配置
在application.yml中配置Sa-Token参数:
sa-token:
# token名称(同时也是cookie名称)
token-name: satoken
# token有效期(单位:秒),默认30天
timeout: 2592000
# token临时有效期,指定时间内无操作则自动刷新
activity-timeout: -1
# 是否允许同一账号多地同时登录
is-concurrent: true
# 多人登录同一账号时是否共用一个token
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: true
2.3 启动类配置
@SpringBootApplication
public class SaTokenApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenApplication.class, args);
// 打印Sa-Token配置信息
System.out.println(SaManager.getConfig());
}
}
三、登录认证实现
3.1 用户服务类
@Service
public class UserService {
/**
* 模拟用户查询
*/
public User findUserByName(String username) {
// 这里应该是数据库查询,这里简单模拟
if ("admin".equals(username)) {
return new User(1L, "admin", "123456", Arrays.asList("admin", "user"));
} else if ("user".equals(username)) {
return new User(2L, "user", "123456", Arrays.asList("user"));
}
return null;
}
@Data
@AllArgsConstructor
public static class User {
private Long id;
private String username;
private String password;
private List<String> roles;
}
}
3.2 登录控制器
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private UserService userService;
/**
* 用户登录
*/
@PostMapping("/login")
public SaResult login(@RequestParam String username, @RequestParam String password) {
// 1. 查询用户
UserService.User user = userService.findUserByName(username);
if (user == null) {
return SaResult.error("用户不存在");
}
// 2. 验证密码(实际项目中密码应该加密存储)
if (!user.getPassword().equals(password)) {
return SaResult.error("密码错误");
}
// 3. 登录并设置权限
StpUtil.login(user.getId());
// 4. 设置角色和权限
StpUtil.getSession().set("user", user);
for (String role : user.getRoles()) {
StpUtil.getRoleList().add(role);
}
return SaResult.ok("登录成功").setData(StpUtil.getTokenInfo());
}
/**
* 用户注销
*/
@PostMapping("/logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok("注销成功");
}
/**
* 查询登录状态
*/
@GetMapping("/isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin())
.setData("loginId:" + StpUtil.getLoginIdDefaultNull());
}
}
3.3 登录原理解析
StpUtil.login()内部完成了以下操作:
- 检查账号是否已登录
- 生成唯一Token并创建Session
- 记录Token活跃时间
- 通知全局监听器
- 将Token注入Cookie
浏览器会自动携带Cookie,后续请求无需手动传递Token。
四、权限校验机制
4.1 实现权限接口
@Component
public class StpInterfaceImpl implements StpInterface {
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 根据loginId查询用户权限
return List.of("user.add", "user.update", "user.get", "user.delete");
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 根据loginId查询用户角色
return List.of("admin", "manager");
}
}
4.2 注解式权限校验
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 查询用户信息(需要登录)
*/
@SaCheckLogin
@GetMapping("/info")
public SaResult info() {
UserService.User user = (UserService.User) StpUtil.getSession().get("user");
return SaResult.data(user);
}
/**
* 添加用户(需要admin角色)
*/
@SaCheckRole("admin")
@PostMapping("/add")
public SaResult addUser() {
return SaResult.ok("添加用户成功");
}
/**
* 删除用户(需要admin角色或user:delete权限)
*/
@SaCheckPermission(value = {"user:delete"}, orRole = "admin")
@DeleteMapping("/delete/{id}")
public SaResult deleteUser(@PathVariable Long id) {
return SaResult.ok("删除用户成功: " + id);
}
/**
* 修改用户(需要登录且拥有user:update权限)
*/
@SaCheckPermission("user:update")
@PutMapping("/update")
public SaResult updateUser() {
return SaResult.ok("修改用户成功");
}
}
4.3 编程式权限校验
@RestController
@RequestMapping("/order")
public class OrderController {
/**
* 创建订单
*/
@PostMapping("/create")
public SaResult createOrder() {
// 编程式校验是否登录
if (!StpUtil.isLogin()) {
return SaResult.error("请先登录");
}
// 编程式校验角色
if (!StpUtil.hasRole("user")) {
return SaResult.error("没有用户权限");
}
// 编程式校验权限
if (!StpUtil.hasPermission("order:create")) {
return SaResult.error("没有创建订单的权限");
}
return SaResult.ok("创建订单成功");
}
}
五、路由拦截器配置
5.1 全局拦截器
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册Sa-Token的注解拦截器,打开注解式鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册注解拦截器,并排除不需要注解鉴权的接口地址
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
5.2 自定义路由拦截器
@Component
public class RouteInterceptor extends SaRouteInterceptor {
/**
* 自定义路由拦截器
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 登录认证 -- 拦截所有路由,并排除/auth/login用于开放登录
SaRouter.match("/**")
.notMatch("/auth/login")
.check(r -> StpUtil.checkLogin());
// 角色认证 -- 拦截以admin开头的路由,必须具有admin角色才能通过
SaRouter.match("/admin/**").check(r -> StpUtil.checkRole("admin"));
// 权限认证 -- 不同模块校验不同权限
SaRouter.match("/user/**").check(r -> StpUtil.checkPermission("user"));
SaRouter.match("/order/**").check(r -> StpUtil.checkPermission("order"));
return true;
}
}
六、Session会话管理
6.1 Session控制器
@RestController
@RequestMapping("/session")
public class SessionController {
/**
* 获取当前Session信息
*/
@GetMapping("/info")
public SaResult sessionInfo() {
return SaResult.data(StpUtil.getSession());
}
/**
* 设置Session值
*/
@PostMapping("/set")
public SaResult setSession(@RequestParam String key, @RequestParam String value) {
StpUtil.getSession().set(key, value);
return SaResult.ok("设置成功");
}
/**
* 获取Session值
*/
@GetMapping("/get")
public SaResult getSession(@RequestParam String key) {
return SaResult.data(StpUtil.getSession().get(key));
}
}
6.2 Session操作API
// 获取当前登录用户ID
Object loginId = StpUtil.getLoginId();
// 获取当前登录用户ID并转换为指定类型
Long userId = StpUtil.getLoginIdAsLong();
String userIdStr = StpUtil.getLoginIdAsString();
// 获取当前登录设备
String device = StpUtil.getLoginDevice();
// 获取Token剩余有效时间(单位:秒)
long timeout = StpUtil.getTokenTimeout();
// 获取Token信息
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
七、集成Redis实现分布式会话
7.1 引入Redis依赖
<!-- Sa-Token整合Redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<version>1.36.0</version>
</dependency>
<!-- Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
7.2 Redis配置
spring:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
sa-token:
# 使用Redis作为持久化层
dao-type: redis
# 是否读取Cookie
is-read-cookie: true
# 是否读取Header
is-read-header: true
7.3 自定义Session存储
@Component
public class CustomSaTokenDao extends SaTokenDaoDefaultImpl {
// 重写数据访问层方法,例如使用MongoDB存储
@Override
public String get(String key) {
// 自定义实现
return super.get(key);
}
@Override
public void set(String key, String value, long timeout) {
// 自定义实现
super.set(key, value, timeout);
}
}
八、前后端分离场景
8.1 登录接口返回Token
@PostMapping("/login")
public Map<String, Object> login(@RequestParam String username, @RequestParam String password) {
StpUtil.login(10001);
return StpUtil.getTokenInfo();
}
8.2 前端存储Token
// login.js
async function login() {
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
const response = await fetch("/auth/login", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `username=${username}&password=${password}`
});
const data = await response.json();
if (data.tokenValue) {
localStorage.setItem(data.tokenName, data.tokenValue);
alert("登录成功");
} else {
alert(data.msg);
}
}
// 请求接口示例
async function getUserList() {
const tokenName = "satoken";
const tokenValue = localStorage.getItem(tokenName);
const response = await fetch("/auth/user/list", {
method: "GET",
headers: { [tokenName]: tokenValue }
});
const text = await response.text();
alert(text);
}
九、高级功能
9.1 踢人下线
@RestController
@RequestMapping("/admin")
public class AdminController {
/**
* 将指定用户踢下线
*/
@PostMapping("/kickout/{loginId}")
public SaResult kickout(@PathVariable Object loginId) {
StpUtil.kickout(loginId);
return SaResult.ok("用户 " + loginId + " 已被踢下线");
}
/**
* 封禁账号
*/
@PostMapping("/disable/{loginId}")
public SaResult disableAccount(@PathVariable Object loginId,
@RequestParam(defaultValue = "30") int days) {
StpUtil.disable(loginId, days * 24 * 60 * 60);
return SaResult.ok("账号已封禁" + days + "天");
}
}
9.2 多端登录模式
9.2.1 单地登录
sa-token:
# 是否允许同一账号多地同时登录
is-concurrent: false
9.2.2 多地登录
sa-token:
# 是否允许同一账号多地同时登录
is-concurrent: true
# 多人登录同一账号时是否共用一个token
is-share: true
9.2.3 同端互斥登录
// 指定设备标识登录
StpUtil.login(10001, "PC");
// 查询当前登录的设备标识
String device = StpUtil.getLoginDevice();
// 指定设备端类型下线
StpUtil.logout(10001, "PC");
9.3 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 拦截所有Sa-Token相关异常
*/
@ExceptionHandler(SaTokenException.class)
public SaResult handlerSaTokenException(SaTokenException e) {
return SaResult.error(e.getMessage());
}
/**
* 拦截所有其他异常
*/
@ExceptionHandler(Exception.class)
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error("系统异常:" + e.getMessage());
}
}
十、性能优化与最佳实践
10.1 缓存优化
sa-token:
# 是否缓存权限信息
is-cache: true
# 权限缓存有效期(单位:秒)
cache-timeout: 3600
10.2 安全配置
sa-token:
# 是否开启CSRF防护
is-csrf: true
# CSRF Token名称
csrf-token-name: satoken-csrf
# 是否开启XSS防护
is-xss: true
10.3 多账号体系
// 多账号登录
StpUtil.login(10001, "user");
StpUtil.login(10001, "admin");
// 多账号注销
StpUtil.logout(10001, "user");
10.4 二级认证
@RestController
@RequestMapping("/safe")
public class SafeController {
/**
* 打开二级认证
*/
@PostMapping("/open")
public SaResult openSafe() {
StpUtil.openSafe(300); // 有效期300秒
return SaResult.ok("二级认证已开启");
}
/**
* 需要二级认证的接口
*/
@SaCheckSafe
@GetMapping("/info")
public SaResult getSafeInfo() {
return SaResult.ok("敏感信息");
}
}
十一、总结
Sa-Token作为一款轻量级Java权限认证框架,通过简洁的API设计和丰富的功能特性,为开发者提供了快速构建权限系统的解决方案。本文从基础集成、登录认证、权限校验、会话管理、分布式部署等多个维度进行了深度解析,帮助开发者全面掌握Sa-Token的使用技巧和最佳实践。
核心优势总结:
- 极简API:一行代码实现登录,注解式权限校验
- 功能全面:支持多种登录模式、权限控制、会话管理、踢人下线等
- 扩展性强:支持Redis分布式会话、自定义存储、多账号体系等
- 性能优异:轻量级设计,响应速度快,支持缓存优化
- 生态完善:中文文档友好,社区活跃,持续更新维护
无论是单体应用还是微服务架构,Sa-Token都能提供稳定可靠的权限认证解决方案,是Java开发者构建权限系统的理想选择。
若内容若侵犯到您的权益,请发送邮件至:platform_service@jienda.com我们将第一时间处理!
所有资源仅限于参考和学习,版权归JienDa作者所有,更多请访问JienDa首页。





