SpringBoot集成Sa-Token权限校验框架深度解析

一、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的使用技巧和最佳实践。

核心优势总结

  1. 极简API:一行代码实现登录,注解式权限校验
  2. 功能全面:支持多种登录模式、权限控制、会话管理、踢人下线等
  3. 扩展性强:支持Redis分布式会话、自定义存储、多账号体系等
  4. 性能优异:轻量级设计,响应速度快,支持缓存优化
  5. 生态完善:中文文档友好,社区活跃,持续更新维护

无论是单体应用还是微服务架构,Sa-Token都能提供稳定可靠的权限认证解决方案,是Java开发者构建权限系统的理想选择。

版权声明:本文为JienDa博主的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
若内容若侵犯到您的权益,请发送邮件至:platform_service@jienda.com我们将第一时间处理!
所有资源仅限于参考和学习,版权归JienDa作者所有,更多请访问JienDa首页。

给TA赞助
共{{data.count}}人
人已赞助
后端

Java 开发日记:消息的可靠性投递深度解析

2025-12-22 9:24:14

后端

解决Java项目中"zip END header not found"错误

2025-12-22 9:31:47

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索