当前位置: 首页 > news >正文

Sa-Token完全学习指南

目录

1. Sa-Token简介

1.1 什么是Sa-Token?

1.2 Sa-Token架构图

1.3 Sa-Token vs 其他框架

1.4 适用场景

2. 环境搭建与快速开始

2.1 Maven依赖

SpringBoot环境

WebFlux环境

2.2 基础配置

application.yml配置

2.3 创建启动类

2.4 第一个登录接口

2.5 统一响应类

2.6 全局异常处理

3. 核心API详解

3.1 StpUtil核心方法

登录相关API

Token相关API

会话查询API

3.2 Session操作API

4. 登录认证机制

4.1 登录流程详解

4.2 多端登录控制

4.3 Remember Me功能

4.4 登录扩展参数

5. 权限验证详解

5.1 权限验证基础

5.2 权限验证API

5.3 权限通配符

5.4 自定义权限验证器

5.5 数据权限控制

6. Session会话管理

6.1 Session基本操作

6.2 Session超时管理

6.3 自定义Session

6.4 Session监听器

7. 注解鉴权

7.1 基础鉴权注解

7.2 复合条件注解

7.3 安全认证注解

7.4 禁用验证注解

7.5 自定义注解

7.6 类级别注解

8. 路由拦截鉴权

8.1 基于拦截器的鉴权

8.2 SaRouter路由匹配

8.3 高级路由配置

8.4 基于Filter的全局鉴权

8.5 WebFlux环境下的路由拦截

9. 多账户体系

9.1 多账户配置

9.2 管理员账户体系

9.3 多账户权限接口实现

9.4 多账户登录接口

9.5 多账户拦截器配置

9.6 多账户注解使用

10. 集成Redis

10.1 Redis依赖配置

10.2 Redis配置

10.3 Redis序列化配置

10.4 Redis存储测试

10.5 自定义Redis操作

10.6 Redis集群配置

10.7 Redis哨兵配置

10.8 性能监控和优化

11. 集成JWT

11.1 JWT依赖配置

11.2 JWT配置

11.3 JWT配置类

11.4 JWT登录接口

11.5 JWT工具类

11.6 JWT拦截器

11.7 JWT微服务鉴权

11.8 JWT与Redis混合模式

12. 微服务使用

12.1 微服务网关鉴权

Spring Cloud Gateway集成

网关配置

网关鉴权配置

网关自定义过滤器

12.2 服务间认证

Feign客户端集成

服务间Token传递

12.3 分布式Session同步

12.4 微服务配置中心集成

12.5 服务链路追踪

13. 单点登录SSO

13.1 SSO基础配置

13.2 SSO认证服务端

13.3 SSO客户端

13.4 SSO配置详解

13.5 自定义SSO处理器

13.6 跨域SSO配置

13.7 SSO统一用户中心

14. OAuth2.0

14.1 OAuth2.0服务端配置

14.2 OAuth2.0服务端接口

14.3 OAuth2.0客户端

14.4 OAuth2.0客户端管理

14.5 OAuth2.0授权记录

14.6 自定义OAuth2.0扩展

15. 配置详解

15.1 完整配置示例

15.2 多环境配置

15.3 自定义配置类

15.4 动态配置更新

15.5 配置校验

15.6 配置加密

16. 插件扩展

16.1 自定义Token生成器

16.2 自定义持久层

16.3 自定义权限验证器

16.4 自定义Session存储

16.5 自定义Context处理器


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-TokenSpring SecurityShiro
学习难度简单复杂中等
代码量中等
扩展性中等
社区活跃度
文档质量优秀良好一般

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: localhostport: 6379password: database: 0timeout: 10slettuce: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)) {// 登录,参数为用户idStpUtil.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));         // 是否为持久Cookiereturn 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");// 获取所有keySet<String> keys = session.keys();return Result.success("Session操作成功").put("name", name).put("age", age).put("keys", keys);}
}

4. 登录认证机制

4.1 登录流程详解

Sa-Token的登录流程分为以下几个步骤:

  1. 用户提交登录信息
  2. 验证用户名密码
  3. 调用StpUtil.login(id)登录
  4. Sa-Token生成Token
  5. 将Token返回给前端
  6. 前端保存Token
  7. 后续请求携带Token
@RestController
@RequestMapping("/auth")
public class AuthController {@Autowiredprivate 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. 保存用户信息到SessionSaSession 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()) {// 记住我:设置较长的有效期,并且设置为持久CookieloginModel.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 {@Autowiredprivate UserService userService;@Autowiredprivate RoleService roleService;@Autowiredprivate PermissionService permissionService;/*** 返回一个账号所拥有的权限码集合*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 根据用户id查询权限列表List<String> permissions = permissionService.getPermissionsByUserId(loginId);// 这里可以添加一些通用权限permissions.add("common.read");return permissions;}/*** 返回一个账号所拥有的角色标识集合*/@Overridepublic 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 {@Autowiredprivate 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", "zhangsan@example.com");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);// 获取所有keySet<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("admin@example.com");session.set("currentUser", user);// 存储列表List<String> hobbies = Arrays.asList("编程", "阅读", "游戏");session.set("hobbies", hobbies);// 存储MapMap<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);// 读取MapMap<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();// 删除指定keysession.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) {// 创建自定义SessionSaSession 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);/** 每次登录时触发 */@Overridepublic void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {log.info("用户登录:loginType={}, loginId={}, tokenValue={}", loginType, loginId, tokenValue);// 记录登录日志recordLoginLog(loginId, tokenValue, "LOGIN");}/** 每次注销时触发 */@Overridepublic void doLogout(String loginType, Object loginId, String tokenValue) {log.info("用户注销:loginType={}, loginId={}, tokenValue={}", loginType, loginId, tokenValue);// 记录注销日志recordLoginLog(loginId, tokenValue, "LOGOUT");}/** 每次被踢下线时触发 */@Overridepublic void doKickout(String loginType, Object loginId, String tokenValue) {log.info("用户被踢下线:loginType={}, loginId={}, tokenValue={}", loginType, loginId, tokenValue);// 记录踢出日志recordLoginLog(loginId, tokenValue, "KICKOUT");}/** 每次被顶下线时触发 */@Overridepublic void doReplaced(String loginType, Object loginId, String tokenValue) {log.info("用户被顶下线:loginType={}, loginId={}, tokenValue={}", loginType, loginId, tokenValue);// 记录顶替日志recordLoginLog(loginId, tokenValue, "REPLACED");}/** 每次被禁用时触发 */@Overridepublic void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {log.info("用户被禁用:loginType={}, loginId={}, service={}, level={}, disableTime={}", loginType, loginId, service, level, disableTime);}/** 每次被解封时触发 */@Overridepublic void doUntieDisable(String loginType, Object loginId, String service) {log.info("用户被解封:loginType={}, loginId={}, service={}", loginType, loginId, service);}/** 每次打开二级认证时触发 */@Overridepublic void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {log.info("二级认证开启:loginType={}, tokenValue={}, service={}, safeTime={}", loginType, tokenValue, service, safeTime);}/** 每次关闭二级认证时触发 */@Overridepublic void doCloseSafe(String loginType, String tokenValue, String service) {log.info("二级认证关闭:loginType={}, tokenValue={}, service={}", loginType, tokenValue, service);}/** 每次创建Session时触发 */@Overridepublic void doCreateSession(String id) {log.info("Session创建:id={}", id);}/** 每次注销Session时触发 */@Overridepublic void doLogoutSession(String id) {log.info("Session注销:id={}", id);}/** 每次Token续期时触发 */@Overridepublic 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:
http://www.lryc.cn/news/582614.html

相关文章:

  • npm 包 scheduler 介绍
  • C++STL-vector
  • 股票数据源对接技术指南:印度尼西亚、印度、韩国
  • 静态路由实验以及核心原理
  • ubuntu24.04安装NFS网络文件系统/ARM开发板NFS挂载
  • 香港风水(原生)林地的逻辑分类器
  • 香港站群服务器价格怎么样?
  • Android UI 组件系列(四):EditText 使用详解与输入限制
  • LabVIEW-GPRS 远程土壤监测
  • Unity开发如何解决iOS闪退问题
  • kotlin中的冷流和热流
  • 5 种备份和恢复安卓短信的方法
  • 理解STM32F103的中断优先级分组
  • C#,js如何对网页超文本内容按行拆分,选择第A-B个字符返回HTM?
  • day55 序列预测任务介绍
  • React Native安卓刘海屏适配终极方案:仅需修改 AndroidManifest.xml!
  • 鸿蒙分布式开发实战指南:让设备协同像操作本地一样简单
  • Jmeter的JDBC数据库连接
  • 基于springboot的非遗传承宣传平台
  • 【Mac开发】Mac 应用 Archive 成功后无法打开?
  • 苹果App上架流程:不用Mac也可以上架的方法
  • WPF之命令
  • 【论文阅读】Improving the Diffusability of Autoencoders
  • gloo 多卡训练
  • curl: (56) OpenSSL SSL_read: Connection reset by peer, errno 104
  • 开发中如何自定义线程池
  • [1-01-01].第50节:泛型 - 泛型的使用
  • 深入了解linux系统—— System V之消息队列和信号量
  • 自动驾驶的“安全基石”:NVIDIA如何用技术守护未来出行
  • 冷链物流配送中心选址与路径优化模型研究