目录
1. Sa-Token简介
1.1 什么是Sa-Token?
Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录、OAuth2.0、微服务网关鉴权等一系列权限相关问题。
核心特点:
- 简单易用:API设计简洁,上手极快
- 功能强大:登录认证、权限认证、踢人下线、自动续签等功能一应俱全
- 高度集成:完美集成SpringBoot、WebFlux、Solon等主流框架
- 多端支持:同时支持多种前端框架:Vue、React、uniapp、小程序等
- 分布式:完美支持分布式系统,微服务架构
- 多账户认证:比如一个系统同时有User表和Admin表,两套账号分开鉴权
1.2 Sa-Token架构图
Sa-Token架构
|
+----------------+----------------+
| | |
登录认证 权限认证 会话管理
| | |
+----+----+ +----+----+ +----+----+
| | | | | |
账号 密码 角色 权限 Session 踢人
验证 加密 验证 验证 会话 下线
1.3 Sa-Token vs 其他框架
特性 | Sa-Token | Spring Security | Shiro |
---|---|---|---|
学习难度 | 简单 | 复杂 | 中等 |
代码量 | 少 | 多 | 中等 |
扩展性 | 强 | 强 | 中等 |
社区活跃度 | 高 | 高 | 低 |
文档质量 | 优秀 | 良好 | 一般 |
1.4 适用场景
- 中小型项目:快速开发,简单易用
- 微服务架构:分布式鉴权,网关鉴权
- 多端应用:APP、小程序、Web等多端统一鉴权
- 单点登录:多个系统统一登录
- OAuth2.0:第三方登录集成
2. 环境搭建与快速开始
2.1 Maven依赖
SpringBoot环境
<!-- pom.xml -->
<dependencies>
<!-- Sa-Token权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
<!-- Sa-Token整合Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.37.0</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- SpringBoot相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
WebFlux环境
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
2.2 基础配置
application.yml配置
server:
port: 8081
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
# Redis配置
spring:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
2.3 创建启动类
@SpringBootApplication
public class SaTokenApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenApplication.class, args);
System.out.println("启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
2.4 第一个登录接口
@RestController
@RequestMapping("/user")
public class UserController {
// 登录接口
@PostMapping("/login")
public Result login(@RequestBody LoginDto loginDto) {
// 1. 验证用户名和密码
if ("admin".equals(loginDto.getUsername()) && "123456".equals(loginDto.getPassword())) {
// 2. 登录成功,调用Sa-Token的登录方法
StpUtil.login(10001);
// 3. 返回token信息
return Result.success("登录成功")
.put("token", StpUtil.getTokenValue())
.put("tokenInfo", StpUtil.getTokenInfo());
}
return Result.error("用户名或密码错误");
}
// 查询登录状态
@GetMapping("/isLogin")
public Result isLogin() {
boolean isLogin = StpUtil.isLogin();
return Result.success("当前登录状态:" + isLogin)
.put("isLogin", isLogin)
.put("loginId", StpUtil.getLoginIdDefaultNull());
}
// 获取用户信息
@GetMapping("/info")
public Result info() {
// 检查是否登录,如果未登录会抛出异常
StpUtil.checkLogin();
// 模拟从数据库获取用户信息
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("userId", StpUtil.getLoginId());
userInfo.put("username", "admin");
userInfo.put("nickname", "管理员");
return Result.success("获取用户信息成功").put("userInfo", userInfo);
}
// 退出登录
@PostMapping("/logout")
public Result logout() {
StpUtil.logout();
return Result.success("退出登录成功");
}
}
2.5 统一响应类
public class Result {
private int code;
private String message;
private Object data;
private Map<String, Object> map = new HashMap<>();
public static Result success(String message) {
Result result = new Result();
result.code = 200;
result.message = message;
return result;
}
public static Result error(String message) {
Result result = new Result();
result.code = 500;
result.message = message;
return result;
}
public Result put(String key, Object value) {
this.map.put(key, value);
return this;
}
// getter/setter方法...
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public Object getData() { return data; }
public void setData(Object data) { this.data = data; }
public Map<String, Object> getMap() { return map; }
public void setMap(Map<String, Object> map) { this.map = map; }
}
2.6 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
// 拦截Sa-Token异常
@ExceptionHandler(NotLoginException.class)
public Result handlerNotLoginException(NotLoginException nle) {
String message = "";
if (nle.getType().equals(NotLoginException.NOT_TOKEN)) {
message = "未提供token";
} else if (nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
message = "token无效";
} else if (nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
message = "token已过期";
} else if (nle.getType().equals(NotLoginException.BE_REPLACED)) {
message = "token已被顶下线";
} else if (nle.getType().equals(NotLoginException.KICK_OUT)) {
message = "token已被踢下线";
} else {
message = "当前会话未登录";
}
// 返回给前端
return Result.error(message).put("code", 401);
}
// 拦截权限异常
@ExceptionHandler(NotPermissionException.class)
public Result handlerNotPermissionException(NotPermissionException e) {
return Result.error("权限不足:" + e.getPermission()).put("code", 403);
}
// 拦截角色异常
@ExceptionHandler(NotRoleException.class)
public Result handlerNotRoleException(NotRoleException e) {
return Result.error("角色不足:" + e.getRole()).put("code", 403);
}
}
3. 核心API详解
3.1 StpUtil核心方法
Sa-Token的所有功能都通过StpUtil类来实现,这是最重要的工具类。
登录相关API
public class LoginController {
// 登录
@PostMapping("/login")
public Result login(@RequestParam String username, @RequestParam String password) {
// 验证用户名密码(省略具体验证逻辑)
if (checkUser(username, password)) {
// 登录,参数为用户id
StpUtil.login(userId);
// 其他登录方式
// StpUtil.login(userId, "PC"); // 指定设备类型登录
// StpUtil.login(userId, true); // 是否为持久Cookie(记住我)
// StpUtil.login(userId, new SaLoginModel()
// .setDevice("PC") // 设备类型
// .setTimeout(60 * 60 * 24 * 7) // 设置token有效期为7天
// .setIsLastingCookie(true)); // 是否为持久Cookie
return Result.success("登录成功");
}
return Result.error("用户名或密码错误");
}
// 检查登录状态
@GetMapping("/isLogin")
public Result isLogin() {
return Result.success("登录状态:" + StpUtil.isLogin());
}
// 检查登录,如果未登录则抛出异常
@GetMapping("/checkLogin")
public Result checkLogin() {
StpUtil.checkLogin();
return Result.success("当前会话已登录");
}
// 退出登录
@PostMapping("/logout")
public Result logout() {
StpUtil.logout();
return Result.success("退出登录成功");
}
// 踢人下线
@PostMapping("/kickout")
public Result kickout(@RequestParam Object userId) {
StpUtil.kickout(userId);
return Result.success("踢人下线成功");
}
// 顶人下线
@PostMapping("/replaced")
public Result replaced(@RequestParam Object userId) {
StpUtil.replaced(userId);
return Result.success("顶人下线成功");
}
}
Token相关API
public class TokenController {
// 获取当前Token值
@GetMapping("/getToken")
public Result getToken() {
String token = StpUtil.getTokenValue();
return Result.success("当前token:" + token);
}
// 获取当前Token信息
@GetMapping("/getTokenInfo")
public Result getTokenInfo() {
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return Result.success("token信息").put("tokenInfo", tokenInfo);
}
// 获取当前Token剩余有效期(单位:秒)
@GetMapping("/getTokenTimeout")
public Result getTokenTimeout() {
long timeout = StpUtil.getTokenTimeout();
return Result.success("token剩余有效期:" + timeout + "秒");
}
// 获取当前Token活跃剩余有效期(单位:秒)
@GetMapping("/getTokenActivityTimeout")
public Result getTokenActivityTimeout() {
long timeout = StpUtil.getTokenActivityTimeout();
return Result.success("token活跃剩余有效期:" + timeout + "秒");
}
// 续签Token(延长其有效期)
@PostMapping("/renewTimeout")
public Result renewTimeout(@RequestParam long timeout) {
StpUtil.renewTimeout(timeout);
return Result.success("续签成功,新的有效期:" + timeout + "秒");
}
}
会话查询API
public class SessionController {
// 获取当前登录用户id
@GetMapping("/getLoginId")
public Result getLoginId() {
Object loginId = StpUtil.getLoginId();
return Result.success("当前登录用户id:" + loginId);
}
// 获取当前登录用户id,如果未登录则返回默认值
@GetMapping("/getLoginIdDefaultNull")
public Result getLoginIdDefaultNull() {
Object loginId = StpUtil.getLoginIdDefaultNull();
return Result.success("当前登录用户id:" + loginId);
}
// 获取当前登录用户id,转为String类型
@GetMapping("/getLoginIdAsString")
public Result getLoginIdAsString() {
String loginId = StpUtil.getLoginIdAsString();
return Result.success("当前登录用户id:" + loginId);
}
// 获取当前登录用户id,转为int类型
@GetMapping("/getLoginIdAsInt")
public Result getLoginIdAsInt() {
int loginId = StpUtil.getLoginIdAsInt();
return Result.success("当前登录用户id:" + loginId);
}
// 获取当前登录用户id,转为long类型
@GetMapping("/getLoginIdAsLong")
public Result getLoginIdAsLong() {
long loginId = StpUtil.getLoginIdAsLong();
return Result.success("当前登录用户id:" + loginId);
}
}
3.2 Session操作API
public class SessionOperateController {
// 获取当前用户的Session对象
@GetMapping("/getSession")
public Result getSession() {
SaSession session = StpUtil.getSession();
return Result.success("session对象").put("sessionId", session.getId());
}
// 获取当前用户的Session对象,如果session尚未创建,是否新建并返回
@GetMapping("/getSession2")
public Result getSession2() {
SaSession session = StpUtil.getSession(false); // false表示不新建
return Result.success("session对象").put("session", session);
}
// 获取指定用户的Session对象
@GetMapping("/getSessionByLoginId")
public Result getSessionByLoginId(@RequestParam Object loginId) {
SaSession session = StpUtil.getSessionByLoginId(loginId);
return Result.success("指定用户的session对象").put("sessionId", session.getId());
}
// 获取指定Token对应的Session对象
@GetMapping("/getSessionByToken")
public Result getSessionByToken(@RequestParam String token) {
SaSession session = StpUtil.getSessionByToken(token);
return Result.success("指定Token的session对象").put("sessionId", session.getId());
}
// Session读写操作示例
@PostMapping("/sessionOperation")
public Result sessionOperation() {
SaSession session = StpUtil.getSession();
// 写值
session.set("name", "张三");
session.set("age", 25);
// 读值
String name = session.getString("name");
int age = session.getInt("age");
// 获取所有key
Set<String> keys = session.keys();
return Result.success("Session操作成功")
.put("name", name)
.put("age", age)
.put("keys", keys);
}
}
4. 登录认证机制
4.1 登录流程详解
Sa-Token的登录流程分为以下几个步骤:
- 用户提交登录信息
- 验证用户名密码
- 调用StpUtil.login(id)登录
- Sa-Token生成Token
- 将Token返回给前端
- 前端保存Token
- 后续请求携带Token
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private UserService userService;
// 完整的登录流程
@PostMapping("/login")
public Result login(@RequestBody LoginRequest loginRequest) {
try {
// 1. 参数校验
if (StrUtil.isBlank(loginRequest.getUsername()) ||
StrUtil.isBlank(loginRequest.getPassword())) {
return Result.error("用户名或密码不能为空");
}
// 2. 根据用户名查询用户
User user = userService.getUserByUsername(loginRequest.getUsername());
if (user == null) {
return Result.error("用户不存在");
}
// 3. 验证密码
if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
return Result.error("密码错误");
}
// 4. 检查用户状态
if (user.getStatus() == 0) {
return Result.error("账户已被禁用");
}
// 5. 执行登录
SaLoginModel loginModel = new SaLoginModel()
.setDevice(loginRequest.getDevice()) // 设备类型
.setTimeout(60 * 60 * 24 * 7) // 7天有效期
.setIsLastingCookie(loginRequest.getRememberMe()); // 记住我
StpUtil.login(user.getId(), loginModel);
// 6. 保存用户信息到Session
SaSession session = StpUtil.getSession();
session.set("user", user);
session.set("loginTime", new Date());
session.set("loginIp", getClientIP());
// 7. 构造返回信息
Map<String, Object> tokenInfo = new HashMap<>();
tokenInfo.put("token", StpUtil.getTokenValue());
tokenInfo.put("tokenName", StpUtil.getTokenName());
tokenInfo.put("tokenTimeout", StpUtil.getTokenTimeout());
tokenInfo.put("sessionTimeout", StpUtil.getSessionTimeout());
tokenInfo.put("tokenSessionTimeout", StpUtil.getTokenSessionTimeout());
tokenInfo.put("tokenActivityTimeout", StpUtil.getTokenActivityTimeout());
tokenInfo.put("loginDevice", StpUtil.getLoginDevice());
return Result.success("登录成功").put("tokenInfo", tokenInfo);
} catch (Exception e) {
log.error("登录异常:", e);
return Result.error("登录失败");
}
}
// 登录请求对象
public static class LoginRequest {
private String username;
private String password;
private String device = "default";
private Boolean rememberMe = false;
// getter/setter...
}
}
4.2 多端登录控制
@RestController
@RequestMapping("/device")
public class DeviceController {
// PC端登录
@PostMapping("/loginPC")
public Result loginPC(@RequestBody LoginRequest request) {
if (validateUser(request)) {
StpUtil.login(request.getUserId(), "PC");
return Result.success("PC登录成功");
}
return Result.error("登录失败");
}
// 移动端登录
@PostMapping("/loginMobile")
public Result loginMobile(@RequestBody LoginRequest request) {
if (validateUser(request)) {
StpUtil.login(request.getUserId(), "Mobile");
return Result.success("移动端登录成功");
}
return Result.error("登录失败");
}
// 获取当前用户所有登录设备
@GetMapping("/getTokenValueListByLoginId")
public Result getTokenValueListByLoginId(@RequestParam Object loginId) {
List<String> tokenList = StpUtil.getTokenValueListByLoginId(loginId);
return Result.success("用户所有token").put("tokenList", tokenList);
}
// 踢掉指定用户在指定设备上的登录
@PostMapping("/kickoutByLoginId")
public Result kickoutByLoginId(@RequestParam Object loginId, @RequestParam String device) {
StpUtil.kickoutByLoginId(loginId, device);
return Result.success("踢掉用户在" + device + "设备上的登录");
}
// 切换到指定设备
@PostMapping("/switchTo")
public Result switchTo(@RequestParam String device) {
StpUtil.switchTo(device);
return Result.success("切换设备成功");
}
}
4.3 Remember Me功能
@RestController
@RequestMapping("/remember")
public class RememberMeController {
// 记住我登录
@PostMapping("/login")
public Result rememberMeLogin(@RequestBody RememberMeRequest request) {
if (validateUser(request.getUsername(), request.getPassword())) {
SaLoginModel loginModel = new SaLoginModel();
if (request.getRememberMe()) {
// 记住我:设置较长的有效期,并且设置为持久Cookie
loginModel.setTimeout(60 * 60 * 24 * 30) // 30天有效期
.setIsLastingCookie(true); // 设置为持久Cookie
} else {
// 不记住我:使用默认配置
loginModel.setTimeout(60 * 60 * 24) // 1天有效期
.setIsLastingCookie(false); // 临时Cookie
}
StpUtil.login(request.getUserId(), loginModel);
return Result.success("登录成功")
.put("rememberMe", request.getRememberMe())
.put("timeout", StpUtil.getTokenTimeout());
}
return Result.error("用户名或密码错误");
}
// 记住我请求对象
public static class RememberMeRequest {
private String username;
private String password;
private Boolean rememberMe = false;
private Object userId;
// getter/setter...
}
}
4.4 登录扩展参数
@RestController
@RequestMapping("/extend")
public class ExtendLoginController {
// 扩展登录信息
@PostMapping("/loginWithExtend")
public Result loginWithExtend(@RequestBody ExtendLoginRequest request) {
if (validateUser(request)) {
// 创建登录模型,设置扩展参数
SaLoginModel loginModel = new SaLoginModel()
.setDevice(request.getDevice())
.setTimeout(request.getTimeout())
.setToken(request.getCustomToken()) // 自定义Token值
.setIsLastingCookie(request.getRememberMe())
.setExtra("loginTime", System.currentTimeMillis()) // 扩展参数
.setExtra("loginIp", getClientIP()) // 扩展参数
.setExtra("userAgent", getUserAgent()); // 扩展参数
StpUtil.login(request.getUserId(), loginModel);
// 获取Token信息,包含扩展参数
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return Result.success("登录成功").put("tokenInfo", tokenInfo);
}
return Result.error("登录失败");
}
// 获取Token扩展参数
@GetMapping("/getExtra")
public Result getExtra() {
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
Map<String, Object> extraData = new HashMap<>();
extraData.put("loginTime", tokenInfo.getExtra("loginTime"));
extraData.put("loginIp", tokenInfo.getExtra("loginIp"));
extraData.put("userAgent", tokenInfo.getExtra("userAgent"));
return Result.success("扩展参数").put("extraData", extraData);
}
}
5. 权限验证详解
5.1 权限验证基础
Sa-Token中的权限验证需要实现StpInterface接口,定义权限和角色的获取逻辑。
@Component
public class StpInterfaceImpl implements StpInterface {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 根据用户id查询权限列表
List<String> permissions = permissionService.getPermissionsByUserId(loginId);
// 这里可以添加一些通用权限
permissions.add("common.read");
return permissions;
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 根据用户id查询角色列表
List<String> roles = roleService.getRolesByUserId(loginId);
return roles;
}
}
5.2 权限验证API
@RestController
@RequestMapping("/permission")
public class PermissionController {
// 验证用户是否具有指定权限
@GetMapping("/checkPermission")
public Result checkPermission(@RequestParam String permission) {
try {
StpUtil.checkPermission(permission);
return Result.success("具有权限:" + permission);
} catch (NotPermissionException e) {
return Result.error("权限不足:" + permission);
}
}
// 验证用户是否具有指定权限(不抛异常)
@GetMapping("/hasPermission")
public Result hasPermission(@RequestParam String permission) {
boolean hasPermission = StpUtil.hasPermission(permission);
return Result.success("权限验证结果:" + hasPermission);
}
// 验证用户是否具有指定权限(任意一个即可)
@GetMapping("/hasPermissionOr")
public Result hasPermissionOr(@RequestParam String[] permissions) {
boolean hasPermission = StpUtil.hasPermissionOr(permissions);
return Result.success("权限验证结果(OR):" + hasPermission)
.put("permissions", permissions);
}
// 验证用户是否具有指定权限(必须全部具有)
@GetMapping("/hasPermissionAnd")
public Result hasPermissionAnd(@RequestParam String[] permissions) {
boolean hasPermission = StpUtil.hasPermissionAnd(permissions);
return Result.success("权限验证结果(AND):" + hasPermission)
.put("permissions", permissions);
}
// 验证用户是否具有指定角色
@GetMapping("/checkRole")
public Result checkRole(@RequestParam String role) {
try {
StpUtil.checkRole(role);
return Result.success("具有角色:" + role);
} catch (NotRoleException e) {
return Result.error("角色不足:" + role);
}
}
// 验证用户是否具有指定角色(不抛异常)
@GetMapping("/hasRole")
public Result hasRole(@RequestParam String role) {
boolean hasRole = StpUtil.hasRole(role);
return Result.success("角色验证结果:" + hasRole);
}
// 获取当前用户的权限列表
@GetMapping("/getPermissionList")
public Result getPermissionList() {
List<String> permissions = StpUtil.getPermissionList();
return Result.success("权限列表").put("permissions", permissions);
}
// 获取当前用户的角色列表
@GetMapping("/getRoleList")
public Result getRoleList() {
List<String> roles = StpUtil.getRoleList();
return Result.success("角色列表").put("roles", roles);
}
}
5.3 权限通配符
Sa-Token支持权限通配符,可以实现更灵活的权限控制。
@RestController
@RequestMapping("/wildcard")
public class WildcardPermissionController {
// 通配符权限示例
@GetMapping("/testWildcard")
public Result testWildcard() {
Map<String, Object> result = new HashMap<>();
// 假设用户具有权限:user.*
// 以下验证都会通过
result.put("user.add", StpUtil.hasPermission("user.add"));
result.put("user.delete", StpUtil.hasPermission("user.delete"));
result.put("user.update", StpUtil.hasPermission("user.update"));
result.put("user.select", StpUtil.hasPermission("user.select"));
// 假设用户具有权限:*.delete
// 以下验证都会通过
result.put("user.delete", StpUtil.hasPermission("user.delete"));
result.put("role.delete", StpUtil.hasPermission("role.delete"));
result.put("permission.delete", StpUtil.hasPermission("permission.delete"));
// 假设用户具有权限:*
// 所有权限验证都会通过(超级管理员)
result.put("any.permission", StpUtil.hasPermission("any.permission"));
return Result.success("通配符权限测试").put("results", result);
}
}
5.4 自定义权限验证器
@Component
public class CustomPermissionValidator {
/**
* 自定义权限验证逻辑
*/
public boolean validateCustomPermission(Object loginId, String resource, String action) {
// 获取用户信息
User user = userService.getUserById(loginId);
if (user == null) {
return false;
}
// 超级管理员拥有所有权限
if ("admin".equals(user.getUsername())) {
return true;
}
// 检查用户是否有访问特定资源的权限
String permission = resource + ":" + action;
List<String> userPermissions = StpUtil.getPermissionList();
// 精确匹配
if (userPermissions.contains(permission)) {
return true;
}
// 通配符匹配
for (String userPermission : userPermissions) {
if (isMatch(userPermission, permission)) {
return true;
}
}
return false;
}
/**
* 通配符匹配逻辑
*/
private boolean isMatch(String pattern, String permission) {
// 实现通配符匹配逻辑
// 例如:user:* 匹配 user:add, user:delete 等
if (pattern.endsWith("*")) {
String prefix = pattern.substring(0, pattern.length() - 1);
return permission.startsWith(prefix);
}
if (pattern.startsWith("*")) {
String suffix = pattern.substring(1);
return permission.endsWith(suffix);
}
return pattern.equals(permission);
}
}
5.5 数据权限控制
@RestController
@RequestMapping("/data")
public class DataPermissionController {
@Autowired
private UserService userService;
// 根据数据权限获取用户列表
@GetMapping("/getUserList")
public Result getUserList() {
Object loginId = StpUtil.getLoginId();
User currentUser = userService.getUserById(loginId);
List<User> userList;
// 根据用户角色决定可以查看的数据范围
if (StpUtil.hasRole("admin")) {
// 管理员:查看所有用户
userList = userService.getAllUsers();
} else if (StpUtil.hasRole("dept_manager")) {
// 部门经理:查看本部门用户
userList = userService.getUsersByDeptId(currentUser.getDeptId());
} else if (StpUtil.hasRole("user")) {
// 普通用户:只能查看自己
userList = Arrays.asList(currentUser);
} else {
// 无权限
return Result.error("无权限查看用户列表");
}
return Result.success("用户列表").put("userList", userList);
}
// 数据权限装饰器
@GetMapping("/getOrderList")
public Result getOrderList(@RequestParam(required = false) String status) {
Object loginId = StpUtil.getLoginId();
// 构建查询条件
OrderQuery query = new OrderQuery();
query.setStatus(status);
// 根据权限添加数据过滤条件
if (StpUtil.hasPermission("order:viewAll")) {
// 有查看所有订单权限,不添加额外条件
} else if (StpUtil.hasPermission("order:viewDept")) {
// 只能查看本部门订单
User currentUser = userService.getUserById(loginId);
query.setDeptId(currentUser.getDeptId());
} else if (StpUtil.hasPermission("order:viewSelf")) {
// 只能查看自己的订单
query.setUserId(loginId);
} else {
return Result.error("无权限查看订单");
}
List<Order> orderList = orderService.getOrderList(query);
return Result.success("订单列表").put("orderList", orderList);
}
}
6. Session会话管理
6.1 Session基本操作
Sa-Token的Session是一个功能强大的会话对象,可以存储任意类型的数据。
@RestController
@RequestMapping("/session")
public class SessionController {
// Session基本读写操作
@PostMapping("/basicOperation")
public Result basicOperation() {
SaSession session = StpUtil.getSession();
// 写入数据
session.set("name", "张三");
session.set("age", 25);
session.set("email", "[email protected]");
session.set("loginTime", new Date());
// 读取数据
String name = session.getString("name");
int age = session.getInt("age");
String email = session.getString("email");
Date loginTime = session.getModel("loginTime", Date.class);
// 获取所有key
Set<String> keys = session.keys();
return Result.success("Session基本操作")
.put("name", name)
.put("age", age)
.put("email", email)
.put("loginTime", loginTime)
.put("keys", keys);
}
// Session存储对象
@PostMapping("/storeObject")
public Result storeObject() {
SaSession session = StpUtil.getSession();
// 存储用户对象
User user = new User();
user.setId(1L);
user.setUsername("admin");
user.setNickname("管理员");
user.setEmail("[email protected]");
session.set("currentUser", user);
// 存储列表
List<String> hobbies = Arrays.asList("编程", "阅读", "游戏");
session.set("hobbies", hobbies);
// 存储Map
Map<String, Object> settings = new HashMap<>();
settings.put("theme", "dark");
settings.put("language", "zh-CN");
session.set("settings", settings);
return Result.success("对象存储成功");
}
// Session读取对象
@GetMapping("/getObject")
public Result getObject() {
SaSession session = StpUtil.getSession();
// 读取用户对象
User user = session.getModel("currentUser", User.class);
// 读取列表
List<String> hobbies = session.getModel("hobbies", List.class);
// 读取Map
Map<String, Object> settings = session.getModel("settings", Map.class);
return Result.success("对象读取成功")
.put("user", user)
.put("hobbies", hobbies)
.put("settings", settings);
}
// Session删除操作
@DeleteMapping("/delete")
public Result delete(@RequestParam String key) {
SaSession session = StpUtil.getSession();
// 删除指定key
session.delete(key);
return Result.success("删除成功:" + key);
}
// 清空Session
@DeleteMapping("/clear")
public Result clear() {
SaSession session = StpUtil.getSession();
session.clear();
return Result.success("Session清空成功");
}
}
6.2 Session超时管理
@RestController
@RequestMapping("/session/timeout")
public class SessionTimeoutController {
// 获取Session剩余有效期
@GetMapping("/getTimeout")
public Result getTimeout() {
SaSession session = StpUtil.getSession();
long timeout = session.getTimeout();
return Result.success("Session剩余有效期:" + timeout + "秒");
}
// 修改Session有效期
@PostMapping("/setTimeout")
public Result setTimeout(@RequestParam long timeout) {
SaSession session = StpUtil.getSession();
session.updateTimeout(timeout);
return Result.success("Session有效期已设置为:" + timeout + "秒");
}
// 获取Session最后活跃时间
@GetMapping("/getLastActiveTime")
public Result getLastActiveTime() {
SaSession session = StpUtil.getSession();
return Result.success("Session最后活跃时间")
.put("createTime", new Date(session.getCreateTime()))
.put("timeout", session.getTimeout());
}
}
6.3 自定义Session
@RestController
@RequestMapping("/session/custom")
public class CustomSessionController {
// 创建自定义Session
@PostMapping("/createCustomSession")
public Result createCustomSession(@RequestParam String sessionId) {
// 创建自定义Session
SaSession session = SaSessionCustomUtil.getSessionById(sessionId);
// 存储数据
session.set("type", "custom");
session.set("createTime", new Date());
session.set("creator", StpUtil.getLoginIdDefaultNull());
return Result.success("自定义Session创建成功")
.put("sessionId", sessionId);
}
// 获取自定义Session
@GetMapping("/getCustomSession")
public Result getCustomSession(@RequestParam String sessionId) {
SaSession session = SaSessionCustomUtil.getSessionById(sessionId, false);
if (session == null) {
return Result.error("Session不存在:" + sessionId);
}
Map<String, Object> data = new HashMap<>();
for (String key : session.keys()) {
data.put(key, session.get(key));
}
return Result.success("自定义Session数据").put("data", data);
}
// 删除自定义Session
@DeleteMapping("/deleteCustomSession")
public Result deleteCustomSession(@RequestParam String sessionId) {
SaSessionCustomUtil.deleteSessionById(sessionId);
return Result.success("自定义Session删除成功:" + sessionId);
}
}
6.4 Session监听器
@Component
public class SessionListener implements SaTokenListener {
private static final Logger log = LoggerFactory.getLogger(SessionListener.class);
/** 每次登录时触发 */
@Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
log.info("用户登录:loginType={}, loginId={}, tokenValue={}", loginType, loginId, tokenValue);
// 记录登录日志
recordLoginLog(loginId, tokenValue, "LOGIN");
}
/** 每次注销时触发 */
@Override
public void doLogout(String loginType, Object loginId, String tokenValue) {
log.info("用户注销:loginType={}, loginId={}, tokenValue={}", loginType, loginId, tokenValue);
// 记录注销日志
recordLoginLog(loginId, tokenValue, "LOGOUT");
}
/** 每次被踢下线时触发 */
@Override
public void doKickout(String loginType, Object loginId, String tokenValue) {
log.info("用户被踢下线:loginType={}, loginId={}, tokenValue={}", loginType, loginId, tokenValue);
// 记录踢出日志
recordLoginLog(loginId, tokenValue, "KICKOUT");
}
/** 每次被顶下线时触发 */
@Override
public void doReplaced(String loginType, Object loginId, String tokenValue) {
log.info("用户被顶下线:loginType={}, loginId={}, tokenValue={}", loginType, loginId, tokenValue);
// 记录顶替日志
recordLoginLog(loginId, tokenValue, "REPLACED");
}
/** 每次被禁用时触发 */
@Override
public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
log.info("用户被禁用:loginType={}, loginId={}, service={}, level={}, disableTime={}",
loginType, loginId, service, level, disableTime);
}
/** 每次被解封时触发 */
@Override
public void doUntieDisable(String loginType, Object loginId, String service) {
log.info("用户被解封:loginType={}, loginId={}, service={}", loginType, loginId, service);
}
/** 每次打开二级认证时触发 */
@Override
public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
log.info("二级认证开启:loginType={}, tokenValue={}, service={}, safeTime={}",
loginType, tokenValue, service, safeTime);
}
/** 每次关闭二级认证时触发 */
@Override
public void doCloseSafe(String loginType, String tokenValue, String service) {
log.info("二级认证关闭:loginType={}, tokenValue={}, service={}", loginType, tokenValue, service);
}
/** 每次创建Session时触发 */
@Override
public void doCreateSession(String id) {
log.info("Session创建:id={}", id);
}
/** 每次注销Session时触发 */
@Override
public void doLogoutSession(String id) {
log.info("Session注销:id={}", id);
}
/** 每次Token续期时触发 */
@Override
public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
log.info("Token续期:tokenValue={}, loginId={}, timeout={}", tokenValue, loginId, timeout);
}
private void recordLoginLog(Object loginId, String tokenValue, String action) {
// 实现登录日志记录逻辑
// 可以保存到数据库或日志文件
}
}
7. 注解鉴权
Sa-Token提供了丰富的注解来实现权限控制,可以在方法上直接使用注解进行鉴权。
7.1 基础鉴权注解
@RestController
@RequestMapping("/admin")
public class AdminController {
// 登录验证:只有登录后才能访问
@SaCheckLogin
@GetMapping("/info")
public Result getAdminInfo() {
return Result.success("管理员信息")
.put("adminId", StpUtil.getLoginId())
.put("adminName", "超级管理员");
}
// 角色验证:必须具有admin角色
@SaCheckRole("admin")
@GetMapping("/userList")
public Result getUserList() {
return Result.success("用户列表")
.put("users", getUserListFromDB());
}
// 权限验证:必须具有user:add权限
@SaCheckPermission("user:add")
@PostMapping("/addUser")
public Result addUser(@RequestBody User user) {
// 添加用户逻辑
return Result.success("用户添加成功");
}
// 权限验证:必须具有user:delete权限
@SaCheckPermission("user:delete")
@DeleteMapping("/deleteUser/{userId}")
public Result deleteUser(@PathVariable Long userId) {
// 删除用户逻辑
return Result.success("用户删除成功");
}
// 权限验证:必须具有user:update权限
@SaCheckPermission("user:update")
@PutMapping("/updateUser")
public Result updateUser(@RequestBody User user) {
// 更新用户逻辑
return Result.success("用户更新成功");
}
// 权限验证:必须具有user:select权限
@SaCheckPermission("user:select")
@GetMapping("/getUser/{userId}")
public Result getUser(@PathVariable Long userId) {
// 查询用户逻辑
return Result.success("用户信息").put("user", getUserFromDB(userId));
}
}
7.2 复合条件注解
@RestCont