1. SpringBoot与sa-token权限框架整合概述
在Java企业级应用开发中,权限管理一直是系统架构的核心模块。传统的Shiro和Spring Security虽然功能强大,但配置复杂、学习曲线陡峭。sa-token作为一个新兴的轻量级权限认证框架,以其简洁的API设计和全面的功能覆盖,正在成为越来越多开发者的选择。
我最近在一个电商后台管理系统中采用了SpringBoot+sa-token的技术方案,仅用两天时间就完成了从零到生产的权限系统搭建。与之前使用Spring Security的项目相比,代码量减少了60%,而功能却更加完善。下面我将分享这套技术组合的实战经验。
2. 环境准备与基础配置
2.1 创建SpringBoot项目
首先使用Spring Initializr创建一个基础项目,选择以下依赖:
- Spring Web
- Lombok
- Spring Data Redis(如需分布式会话)
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>2.2 引入sa-token依赖
在pom.xml中添加sa-token的核心依赖:
<!-- Sa-Token 权限认证 --> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.45.0</version> </dependency> <!-- 如需Redis集成 --> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-dao-redis</artifactId> <version>1.45.0</version> </dependency>2.3 基础配置
在application.yml中添加最小化配置:
sa-token: # token名称(同时也是cookie名称) token-name: satoken # token有效期(单位:秒)默认30天 timeout: 2592000 # token临时有效期(单位:秒) 默认7天 activity-timeout: 604800 # 是否允许同一账号并发登录(为true时允许一起登录,为false时新登录挤掉旧登录) is-concurrent: true # 在多人登录同一账号时,是否共用一个token(为true时所有登录共用一个token,为false时每次登录新建一个token) is-share: false # token风格(可选:uuid、simple-uuid、random-32、random-64、random-128、tik) token-style: uuid提示:生产环境建议配置Redis作为持久层,以保证服务重启后会话不丢失。只需添加redis配置和sa-token-dao-redis依赖即可自动生效。
3. 核心功能实现
3.1 登录认证实现
创建AuthController处理认证逻辑:
@RestController @RequestMapping("/auth") public class AuthController { @PostMapping("/login") public JsonResult login(@RequestBody LoginDto dto) { // 模拟数据库校验 if(!"admin".equals(dto.getUsername()) || !"123456".equals(dto.getPassword())) { return JsonResult.error("账号或密码错误"); } // 会话登录,参数填登录人的账号id StpUtil.login(10001); // 返回token信息 return JsonResult.success(StpUtil.getTokenInfo()); } @GetMapping("/isLogin") public JsonResult isLogin() { return JsonResult.success(StpUtil.isLogin()); } @GetMapping("/logout") public JsonResult logout() { StpUtil.logout(); return JsonResult.success(); } }3.2 权限校验实现
sa-token提供多种权限校验方式,下面介绍最常用的三种:
3.2.1 注解式鉴权
@RestController @RequestMapping("/user") public class UserController { // 登录校验:只有登录后才能进入该方法 @SaCheckLogin @GetMapping("/info") public JsonResult info() { return JsonResult.success("用户信息"); } // 权限校验:必须具有user:add权限才能进入 @SaCheckPermission("user:add") @PostMapping("/add") public JsonResult add() { return JsonResult.success("添加用户成功"); } // 角色校验:必须具有admin角色才能进入 @SaCheckRole("admin") @GetMapping("/admin") public JsonResult admin() { return JsonResult.success("管理员操作"); } }3.2.2 路由拦截式鉴权
创建配置类实现WebMvcConfigurer:
@Configuration public class SaTokenConfigure implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 注册Sa-Token的路由拦截器 registry.addInterceptor(new SaInterceptor(handler -> { // 登录校验 -- 拦截所有路由 SaRouter.match("/**", r -> StpUtil.checkLogin()); // 细粒度权限校验 SaRouter.match("/user/**", r -> StpUtil.checkPermission("user")); SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin")); // 更复杂的权限组合 SaRouter.match("/order/**", () -> { StpUtil.checkLogin(); StpUtil.checkPermissionOr("order", "super-admin"); } ); })).addPathPatterns("/**"); } }3.2.3 编程式鉴权
在业务代码中直接调用API:
public void businessMethod() { // 校验是否登录 if(!StpUtil.isLogin()) { throw new RuntimeException("请先登录"); } // 校验权限 StpUtil.checkPermission("user:delete"); // 或关系权限校验(具有任意一个即可) StpUtil.checkPermissionOr("user:delete", "user:update"); // 且关系权限校验(必须全部具有) StpUtil.checkPermissionAnd("user:delete", "user:update"); }3.3 会话管理
sa-token提供了丰富的会话管理API:
// 获取当前会话的token值 String tokenValue = StpUtil.getTokenValue(); // 获取当前会话的账号id Object loginId = StpUtil.getLoginId(); // 获取当前会话的token信息 SaTokenInfo tokenInfo = StpUtil.getTokenInfo(); // 会话注销 StpUtil.logout(); // 踢人下线(将账号为10001的会话踢下线) StpUtil.kickout(10001); // 账号封禁(将账号为10001的会话封禁600秒) StpUtil.disable(10001, 600); // 判断账号是否被封禁 StpUtil.isDisable(10001); // 获取指定token对应的账号id Object loginIdByToken = StpUtil.getLoginIdByToken(token);4. 高级功能实现
4.1 记住我模式
在登录时指定rememberMe参数:
@PostMapping("/login") public JsonResult login(@RequestBody LoginDto dto) { // 参数校验... // 第二个参数指定是否为"记住我"模式 StpUtil.login(10001, dto.isRememberMe()); return JsonResult.success(); }在配置文件中设置rememberMe的超时时间:
sa-token: # rememberMe有效期(单位:秒)默认7天 timeout: 6048004.2 同端互斥登录
在配置中开启同端互斥登录:
sa-token: # 是否允许同一账号并发登录(为true时允许一起登录,为false时新登录挤掉旧登录) is-concurrent: false # 在多人登录同一账号时,是否共用一个token(为true时所有登录共用一个token,为false时每次登录新建一个token) is-share: false4.3 二级认证
对于敏感操作,可以要求进行二级认证:
// 要求当前会话必须已完成二级认证 @SaCheckSafe @PostMapping("/transfer") public JsonResult transfer() { return JsonResult.success("转账成功"); } // 在Controller中提供二级认证接口 @PostMapping("/verifySecond") public JsonResult verifySecond(@RequestBody SecondAuthDto dto) { if(!"123456".equals(dto.getPassword())) { return JsonResult.error("密码错误"); } // 打开二级认证,有效期为120秒 StpUtil.openSafe(120); return JsonResult.success(); }4.4 集成Redis
添加Redis配置和依赖后,只需在配置文件中指定Redis信息:
spring: redis: host: 127.0.0.1 port: 6379 password: database: 0 sa-token: # 是否使用redis进行会话持久化 is-share: true5. 最佳实践与常见问题
5.1 权限数据存储方案
推荐两种权限数据存储方案:
实时查询方案:
@Component public class StpInterfaceImpl implements StpInterface { @Override public List<String> getPermissionList(Object loginId, String loginType) { // 从数据库实时查询该用户拥有的权限码 return permissionService.getPermissionList(loginId); } @Override public List<String> getRoleList(Object loginId, String loginType) { // 从数据库实时查询该用户拥有的角色码 return roleService.getRoleList(loginId); } }缓存方案:
@Component public class StpInterfaceImpl implements StpInterface { @Override public List<String> getPermissionList(Object loginId, String loginType) { String key = "user:perms:" + loginId; List<String> perms = redisTemplate.opsForList().range(key, 0, -1); if(CollectionUtils.isEmpty(perms)) { perms = permissionService.getPermissionList(loginId); redisTemplate.opsForList().rightPushAll(key, perms); redisTemplate.expire(key, 2, TimeUnit.HOURS); } return perms; } }
5.2 常见问题解决方案
问题1:权限变更后如何立即生效?
解决方案:
// 当用户权限变更时,调用以下方法清除缓存 StpUtil.getSessionByLoginId(loginId).delete("Permission_List"); StpUtil.getSessionByLoginId(loginId).delete("Role_List"); // 或者直接踢用户下线强制重新登录 StpUtil.kickout(loginId);问题2:如何自定义token生成策略?
解决方案:
@Configuration public class TokenConfig { @Autowired public void rewriteSaStrategy() { // 重写Token生成策略 SaStrategy.me.createToken = (loginId, loginType) -> { return "自定义前缀-" + IdUtil.fastSimpleUUID(); }; } }问题3:前后端分离架构下如何传递token?
解决方案:
- 前端在登录后保存返回的token
- 在后续请求的header中携带:
axios.defaults.headers.common['satoken'] = localStorage.getItem('token') - 后端配置允许header跨域:
sa-token: # 配置token读取的header名称 token-name: satoken
5.3 性能优化建议
会话存储优化:
- 对于高频访问但数据量小的会话,使用本地缓存
- 对于低频访问或数据量大的会话,使用Redis
权限校验优化:
// 使用缓存注解减少数据库查询 @Cacheable(value = "user_perms", key = "#loginId") public List<String> getPermissionList(String loginId) { // 数据库查询 }日志监控:
@Component public class SaTokenListener implements SaTokenListener { @Override public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) { // 记录登录日志 } @Override public void doLogout(String loginType, Object loginId, String tokenValue) { // 记录登出日志 } }
6. 安全防护措施
6.1 防重复登录攻击
在配置中限制同一账号的登录设备数:
sa-token: # 同一账号最大登录数量(-1表示不限制) max-login-count: 36.2 敏感操作二次验证
对关键操作添加二级认证:
@SaCheckSafe @PostMapping("/changePassword") public JsonResult changePassword() { // 修改密码逻辑 }6.3 定期更换token密钥
@Scheduled(cron = "0 0 0 * * ?") // 每天凌晨执行 public void refreshTokenSecret() { SaManager.getSaTokenDao().updateSecretKey(); }6.4 接口防刷限流
结合sa-token的注解实现:
@SaCheckRole("admin") @SaCheckSafe @SaCheckLimit(value = 5, time = 60, key = "changePassword") // 60秒内最多5次 @PostMapping("/changePassword") public JsonResult changePassword() { // 修改密码逻辑 }在实际项目中,SpringBoot与sa-token的整合展现出了极高的开发效率和运行性能。通过合理的架构设计,我们构建的权限系统不仅满足了基础的身份认证和权限控制需求,还实现了分布式会话、安全防护等高级特性。相比传统方案,开发周期缩短了50%以上,而系统稳定性和可维护性却得到了显著提升。