SpringBoot 整合 Sa-Token
SpringBoot 整合 Sa-Token
一、前言 & 技术简介
1.1 什么是 Sa-Token
官网:https://sa-token.cc/index.html
Sa-Token 是一款轻量级 Java 权限认证框架,主打:登录认证、权限认证、角色认证、单点登录、会话管理,相比 SpringSecurity、Shiro 更加轻量、开箱即用、代码极简、学习成本极低。
1.2 RBAC 权限模型
RBAC 权限模型(用户-角色-权限):
-
用户(User):具体操作者(admin、test)
-
角色(Role):权限集合载体(超级管理员、运营人员)
-
权限(Permission):接口粒度权限标识(user:add、user:delete、user:edit)
核心关系:用户分配角色 → 角色绑定权限 → 用户拥有对应权限
1.3 本项目实现功能
-
账号密码 BCrypt 加密登录、退出登录、获取当前登录用户
-
基于 注解鉴权:角色鉴权、权限鉴权
-
基于 路由拦截鉴权:全局登录拦截、放行指定接口
-
完整用户 CRUD 接口(分页查询、新增、修改、删除、批量删除、状态修改)
-
全局异常统一处理(401未登录、403无权限)
-
密码加密、防重复用户名、禁止删除超级管理员
-
前后端分离跨域配置、Token 会话管理
1.4 技术栈
-
SpringBoot 2.7.x
-
Sa-Token 1.37.0(最新稳定版)
-
MyBatis-Plus 3.5.x
-
MySQL 8.0
-
Lombok、Hutool
二、数据库设计
标准 RBAC 五张表:用户表、角色表、用户角色关联表、权限表、角色权限关联表
-- 创建数据库
CREATE DATABASE IF NOT EXISTS springboot_satoken_demo DEFAULT CHARACTER SET utf8mb4;
USE springboot_satoken_demo;
-- 1. 用户表
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '登录账号',
`password` varchar(100) NOT NULL COMMENT '加密密码',
`nickname` varchar(50) DEFAULT NULL COMMENT '用户昵称',
`status` tinyint DEFAULT 1 COMMENT '状态 1正常 0禁用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY uk_username (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';
-- 2. 角色表
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`role_key` varchar(50) NOT NULL COMMENT '角色标识',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY uk_role_key (`role_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- 3. 用户角色关联表
CREATE TABLE `sys_user_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
PRIMARY KEY (`id`),
UNIQUE KEY uk_user_role (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
-- 4. 权限表
CREATE TABLE `sys_permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`perm_key` varchar(100) NOT NULL COMMENT '权限标识',
`perm_name` varchar(50) NOT NULL COMMENT '权限名称',
PRIMARY KEY (`id`),
UNIQUE KEY uk_perm_key (`perm_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
-- 5. 角色权限关联表
CREATE TABLE `sys_role_perm` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_id` bigint NOT NULL,
`perm_id` bigint NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY uk_role_perm (`role_id`,`perm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';
-- ===================== 初始化测试数据 =====================
-- 密码统一:123456
insert into `sys_user`(`id`,`username`,`password`,`nickname`,`status`,`create_time`) values
(1,'admin','$2a$10$uxLWDO3ZUObjeXaIsM7vc.8k6nxVHKgx0h8YTEQzh6iJD5pxmO.em','超级管理员',1,'2026-07-06 14:55:08'),
(2,'test','$2a$10$uxLWDO3ZUObjeXaIsM7vc.8k6nxVHKgx0h8YTEQzh6iJD5pxmO.em','运营员工',1,'2026-07-06 14:55:08');
-- 角色
INSERT INTO sys_role(role_name,role_key) VALUES
('超级管理员','admin'),
('运营人员','operator');
-- 用户分配角色
INSERT INTO sys_user_role(user_id,role_id) VALUES (1,1),(2,2);
-- 权限(适配本项目全部接口)
INSERT INTO sys_permission(perm_key,perm_name) VALUES
('sys:dashboard','查看首页'),
('user:list','用户列表查询'),
('user:add','新增用户'),
('user:edit','编辑用户'),
('user:delete','删除用户');
-- admin 拥有所有权限
INSERT INTO sys_role_perm(role_id,perm_id) VALUES
(1,1),(1,2),(1,3),(1,4),(1,5),
-- operator 仅拥有查询权限
(2,1),(2,2);
三、项目创建 & 依赖配置

3.1 pom.xml 核心依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/>
</parent>
<groupId>com.yqd</groupId>
<artifactId>SpringBoot-SaToken-Demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBoot-SaToken-Demo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token SpringBoot 整合包 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Hutool工具 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.2 application.yml 配置(修复所有过期警告)
已修复:activity-timeout 过期、端口冲突规避、前后端分离配置
# 服务端口
server:
port: 8080
# 数据库配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/springboot_satoken_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.yqd.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# Sa-Token 全局配置
sa-token:
# token名称
token-name: satoken
# token有效期 单位s 30分钟
timeout: 1800
# 临时有效期 无操作自动续期
active-timeout: 1200
# 单设备登录
is-single-login: true
# 关闭cookie自动注入(前后端分离推荐)
is-read-cookie: false
# 打印日志
is-print: true
四、核心代码实现
4.1 统一返回结果 Result
package com.yqd.common;
import lombok.Data;
@Data
public class Result<T> {
private Integer code;
private String msg;
private T data;
public static <T> Result<T> ok(T data) {
Result<T> r = new Result<>();
r.setCode(200);
r.setMsg("操作成功");
r.setData(data);
return r;
}
public static <T> Result<T> fail(Integer code, String msg) {
Result<T> r = new Result<>();
r.setCode(code);
r.setMsg(msg);
return r;
}
}
4.2 全局异常处理器
package com.yqd.common;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 未登录
@ExceptionHandler(NotLoginException.class)
public Result<?> notLogin(NotLoginException e) {
return Result.fail(401, "用户未登录,请先登录");
}
// 无角色
@ExceptionHandler(NotRoleException.class)
public Result<?> noRole(NotRoleException e) {
return Result.fail(403, "当前账号无访问角色权限:" + e.getRole());
}
// 无权限
@ExceptionHandler(NotPermissionException.class)
public Result<?> noPerm(NotPermissionException e) {
return Result.fail(403, "当前账号无操作权限:" + e.getPermission());
}
// 其他异常
@ExceptionHandler(Exception.class)
public Result<?> otherError(Exception e) {
e.printStackTrace();
return Result.fail(500, "系统异常:" + e.getMessage());
}
}
4.3 实体类
SysUser
package com.yqd.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_user")
public class SysUser {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String nickname;
private Integer status;
private LocalDateTime createTime;
}
SysRole
package com.yqd.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_role")
public class SysRole {
@TableId(type = IdType.AUTO)
private Long id;
private String roleName;
private String roleKey;
private LocalDateTime createTime;
}
4.4 Mapper 层
package com.yqd.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yqd.entity.SysUser;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface SysUserMapper extends BaseMapper<SysUser> {
// 根据用户ID查询角色标识集合
@Select("SELECT r.role_key FROM sys_role r " +
"LEFT JOIN sys_user_role ur ON r.id = ur.role_id " +
"WHERE ur.user_id = #{userId}")
List<String> getRoleKeysByUserId(@Param("userId") Long userId);
// 根据用户ID查询权限标识集合
@Select("SELECT p.perm_key FROM sys_permission p " +
"LEFT JOIN sys_role_perm rp ON p.id = rp.perm_id " +
"LEFT JOIN sys_user_role ur ON rp.role_id = ur.role_id " +
"WHERE ur.user_id = #{userId}")
List<String> getPermKeysByUserId(@Param("userId") Long userId);
}
4.5 Service 层
package com.yqd.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yqd.entity.SysUser;
import com.yqd.mapper.SysUserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class UserService extends ServiceImpl<SysUserMapper, SysUser> {
@Resource
private SysUserMapper userMapper;
// 根据用户名查询用户
public SysUser getUserByUsername(String username) {
return this.lambdaQuery().eq(SysUser::getUsername, username).one();
}
// 获取用户角色标识
public List<String> getUserRoleList(Long userId) {
return userMapper.getRoleKeysByUserId(userId);
}
// 获取用户权限标识
public List<String> getUserPermList(Long userId) {
return userMapper.getPermKeysByUserId(userId);
}
}
4.6配置类
Sa-Token 核心配置类
package com.yqd.config;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.yqd.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
import java.util.List;
/**
* Sa-Token 权限自定义实现 + 路由拦截配置
*/
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Resource
private UserService userService;
// ===================== 1. 自定义权限数据源(核心RBAC) =====================
@Bean
public StpInterface stpInterface() {
return new StpInterface() {
/**
* 获取当前登录用户的权限集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
Long userId = Long.parseLong(loginId.toString());
return userService.getUserPermList(userId);
}
/**
* 获取当前登录用户的角色集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
Long userId = Long.parseLong(loginId.toString());
return userService.getUserRoleList(userId);
}
};
}
// ===================== 2. 注册拦截器,配置路由鉴权 =====================
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handler -> {
// 只写权限校验,不用处理登录放行
SaRouter.match("/user/delete", r -> StpUtil.checkRole("admin"));
SaRouter.match("/user/add", r -> StpUtil.checkPermission("user:add"));
// 全局强制登录校验
SaRouter.match("/**", r -> StpUtil.checkLogin());
}))
.addPathPatterns("/**")
// 此处统一放行登录接口,框架自动跳过校验
.excludePathPatterns("/auth/login");
}
// ===================== 3. 全局跨域、统一返回过滤器(前后端分离必备) =====================
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
.addInclude("/**")
.setAuth(obj -> {
// 无需额外处理,拦截器已鉴权
})
.setBeforeAuth(obj -> {
// 跨域配置
SaHolder.getResponse()
.setHeader("Access-Control-Allow-Origin", "*")
.setHeader("Access-Control-Allow-Methods", "*")
.setHeader("Access-Control-Max-Age", "3600")
.setHeader("Access-Control-Allow-Headers", "*");
});
}
}
MyBatisPlus配置类
package com.yqd.common;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
@Configuration
public class MyBatisPlusConfig {
// MyBatis-Plus分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
4.7 启动类
package com.yqd;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.yqd.mapper")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
4.8 权限控制器 AuthController
package com.yqd.controller;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import com.yqd.common.Result;
import com.yqd.entity.SysUser;
import com.yqd.service.UserService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/auth")
public class AuthController {
@Resource
private UserService userService;
/**
* 登录接口
*/
@PostMapping("/login")
public Result<?> login(@RequestParam String username, @RequestParam String password) {
// 1. 查询用户
SysUser user = userService.getUserByUsername(username);
if (user == null) {
return Result.fail(400, "账号不存在");
}
if (user.getStatus() == 0) {
return Result.fail(400, "账号已禁用");
}
// 2. 密码校验 BCrypt
if (!BCrypt.checkpw(password, user.getPassword())) {
return Result.fail(400, "密码错误");
}
// 3. Sa-Token 登录,传入用户ID作为登录标识
StpUtil.login(user.getId());
// 4. 返回token
Map<String, Object> map = new HashMap<>();
map.put("token", StpUtil.getTokenValue());
map.put("userInfo", user);
return Result.ok(map);
}
/**
* 退出登录
*/
@PostMapping("/logout")
public Result<?> logout() {
StpUtil.logout();
return Result.ok("退出成功");
}
/**
* 获取当前登录用户信息
*/
@RequestMapping("/current")
public Result<?> current() {
Long userId = StpUtil.getLoginIdAsLong();
SysUser user = userService.getById(userId);
return Result.ok(user);
}
}
4.9 用户完整CRUD控制器 UserController
package com.yqd.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yqd.common.Result;
import com.yqd.entity.SysUser;
import com.yqd.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 分页查询用户列表(仅登录即可访问)
*
* @param pageNum 当前页
* @param pageSize 每页条数
* @param username 用户名模糊查询
*/
@GetMapping("/list")
public Result<Page<SysUser>> userList(
@RequestParam(defaultValue = "1") Long pageNum,
@RequestParam(defaultValue = "10") Long pageSize,
@RequestParam(required = false) String username
) {
Page<SysUser> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
// 用户名模糊查询
if (StrUtil.isNotBlank(username)) {
wrapper.like(SysUser::getUsername, username);
}
// 不返回密码明文
wrapper.select(SysUser::getId, SysUser::getUsername, SysUser::getNickname, SysUser::getStatus, SysUser::getCreateTime);
Page<SysUser> pageData = userService.page(page, wrapper);
return Result.ok(pageData);
}
/**
* 根据ID查询单条用户详情
*/
@GetMapping("/{id}")
public Result<SysUser> getInfo(@PathVariable Long id) {
SysUser user = userService.getById(id);
// 清空密码,避免明文返回前端
if (user != null) {
user.setPassword(null);
}
return Result.ok(user);
}
/**
* 新增用户:需要 user:add 权限
*/
@PostMapping
@SaCheckPermission("user:add")
public Result<String> addUser(@RequestBody SysUser user) {
// 参数简单校验
if (StrUtil.isBlank(user.getUsername())) {
return Result.fail(400, "用户名不能为空");
}
if (StrUtil.isBlank(user.getPassword())) {
return Result.fail(400, "密码不能为空");
}
// 判断用户名是否重复
SysUser existUser = userService.getUserByUsername(user.getUsername());
if (existUser != null) {
return Result.fail(400, "该用户名已存在");
}
// BCrypt加密密码
user.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()));
// 默认启用状态
if (user.getStatus() == null) {
user.setStatus(1);
}
userService.save(user);
return Result.ok("新增用户成功");
}
/**
* 修改用户:需要 user:edit 权限
*/
@PutMapping
@SaCheckPermission("user:edit")
public Result<String> editUser(@RequestBody SysUser user) {
if (user.getId() == null) {
return Result.fail(400, "用户ID不能为空");
}
// 查询原有用户
SysUser dbUser = userService.getById(user.getId());
if (dbUser == null) {
return Result.fail(400, "用户不存在");
}
// 如果前端传了新密码,则重新加密;不传则保留原密码
if (StrUtil.isNotBlank(user.getPassword())) {
user.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()));
} else {
user.setPassword(null);
}
userService.updateById(user);
return Result.ok("修改用户成功");
}
/**
* 删除单个用户:需要 admin角色 + user:delete权限
*/
@DeleteMapping("/{id}")
@SaCheckRole("admin")
@SaCheckPermission("user:delete")
public Result<String> deleteUser(@PathVariable Long id) {
// 禁止删除超级管理员id=1
if (id == 1) {
return Result.fail(400, "不允许删除超级管理员账号");
}
userService.removeById(id);
return Result.ok("删除用户成功");
}
/**
* 批量删除用户
*/
@DeleteMapping("/batch")
@SaCheckRole("admin")
@SaCheckPermission("user:delete")
public Result<String> batchDelete(@RequestParam List<Long> ids) {
// 过滤掉超级管理员ID
if (ids.contains(1L)) {
return Result.fail(400, "不允许删除超级管理员账号");
}
userService.removeByIds(ids);
return Result.ok("批量删除成功");
}
/**
* 修改用户状态(启用/禁用)
*/
@PutMapping("/status")
@SaCheckPermission("user:edit")
public Result<String> changeStatus(@RequestParam Long id, @RequestParam Integer status) {
if (status != 0 && status != 1) {
return Result.fail(400, "状态参数非法");
}
SysUser user = new SysUser();
user.setId(id);
user.setStatus(status);
userService.updateById(user);
return Result.ok("状态修改成功");
}
}
五、完整测试流程
5.1 测试账号
-
超级管理员:admin / 123456 (admin角色,全部权限)
-
运营账号:test / 123456 (operator角色,仅查询权限)
5.2 测试前置
-
项目启动端口:8081
-
所有业务接口请求头携带:
satoken: 登录返回的token
5.3 测试步骤
1、未登录访问任意接口
GET/user/list → 返回 401 未登录
2、登录获取Token
POST /auth/login 传参 username、password
3、admin 账号全权限测试
所有 CRUD 接口全部正常通过
4、test 账号权限拦截测试
-
✅ /user/list、/user/{id} 正常访问
-
❌ 新增、修改、删除 全部 403 无权限
5、退出登录
POST /auth/logout,旧Token立即失效

浙公网安备 33010602011771号