一、背景与核心概念
在微服务架构中,接口级权限控制是保障系统安全的重要环节。基于 RBAC(Role-Based Access Control)模型,结合轻量级权限认证框架 SaToken,可实现细粒度的接口权限控制。核心设计思路如下:
- 角色权限解耦:通过用户-角色-权限三级结构实现权限分配
- 动态鉴权:基于接口绑定权限标识实现动态验证
- 最小权限原则:通过白名单与细粒度注解控制访问边界
二、数据库设计关键点
1. 核心表结构
# 用户表
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_email` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '邮箱',
`user_phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号',
`user_password` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '密码',
`user_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户昵称',
`user_avatar` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户头像',
`user_profile` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户简介',
`user_card` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '身份证号',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted_flag` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户' ROW_FORMAT = Dynamic;
# 菜单表
DROP TABLE IF EXISTS `tb_menu`;
CREATE TABLE `tb_menu` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单id',
`menu_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单名称',
`menu_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单类型:目录,菜单',
`parent_id` bigint NULL DEFAULT NULL COMMENT '父菜单id',
`order_num` int NULL DEFAULT NULL COMMENT '显示顺序',
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由地址',
`component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径',
`perm_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识键',
`disabled_flag` tinyint(1) NULL DEFAULT 0 COMMENT '禁用状态',
`deleted_flag` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除状态',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单表' ROW_FORMAT = DYNAMIC;
# 角色和菜单关联表
DROP TABLE IF EXISTS `tb_role_menu`;
CREATE TABLE `tb_role_menu` (
`role_id` bigint NOT NULL COMMENT '角色id',
`menu_id` bigint NOT NULL COMMENT '菜单'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic;
# 角色表
DROP TABLE IF EXISTS `tb_role`;
CREATE TABLE `tb_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色id',
`role_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色名称',
`role_key` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色权限字符串',
`role_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色状态(0、正常;1、禁用)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
# 用户和角色关联表
DROP TABLE IF EXISTS `tb_user_role`;
CREATE TABLE `tb_user_role` (
`user_id` bigint NOT NULL COMMENT '用户id',
`role_id` bigint NOT NULL COMMENT '角色id'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic;
2. 权限标识规范
- 采用
资源:操作
格式(如:user:add
) - 支持通配符(如:
user:*
表示用户所有操作)
三、SaToken 集成实现
1. 依赖配置
<!-- Sa-Token 核心包 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.37.0</version>
</dependency>
<!-- Redis 集成(推荐生产环境使用) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis</artifactId>
<version>1.37.0</version>
</dependency>
2. 权限加载实现
@Component
public class StpInterfaceImpl implements StpInterface {
@Resource
private AuthService authService;
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return authService.loadPermissions((Long)loginId);
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return authService.loadRoles((Long)loginId);
}
}
/**
* 自定义权限接口实现类 获取用户权限和角色
*/
@Component
public class StpInterfaceImpl implements StpInterface {
@Resource
private AuthService authService;
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return authService.getPermissionList(loginId);
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return authService.getRoleList(loginId);
}
}
这是可以看到所有的权限标识
权限服务实现要点:
- 使用
@Cacheable
注解缓存权限数据 - 角色变更时需清理缓存
- 建议使用批量查询优化性能
四、权限拦截配置
1. 全局拦截器配置
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
private final AntPathMatcher pathMatcher = new AntPathMatcher();
// 动态白名单(可从配置中心加载)
private static final List<String> WHITE_LIST = Arrays.asList(
"/public/**",
"/swagger/**",
"/v3/api-docs/**"
);
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handler -> {
// 动态鉴权逻辑
SaRouter
.match("/**") // 拦截所有路径
.notMatch(WHITE_LIST) // 排除白名单
.check(r -> {
// 登录检查
StpUtil.checkLogin();
// 权限检查(通过注解更灵活)
});
})).addPathPatterns("/**");
}
}
优化建议:这里直接放行了路径第二个是 public的接口
2. 注解式权限控制
// 角色校验
@SaCheckRole("admin")
@PostMapping("/users")
public Result<?> createUser() { ... }
// 权限校验
@SaCheckPermission("user:delete")
@DeleteMapping("/users/{id}")
public Result<?> deleteUser() { ... }
// 复合校验(满足任意条件)
@SaCheckOr(
permission = "log:export",
role = "audit_admin"
)
@GetMapping("/logs/export")
public void exportLogs() { ... }
如果没有这些标识符,那么将无法访问这个接口
五、动态权限管理方案
1. 权限更新策略
- 实时性要求高:通过 Redis Pub/Sub 通知节点更新缓存
- 一般场景:设置合理的缓存过期时间(建议 5-10 分钟)
- 用户级更新:修改权限后调用
StpUtil.logoutByLoginId(userId)
2. 管理接口示例
@RestController
@RequestMapping("/system/auth")
public class AuthController {
@PostMapping("/role/bind")
@SaCheckPermission("system:role:edit")
public Result<?> bindRolePermission(
@RequestParam String roleKey,
@RequestBody List<String> permKeys) {
// 实现角色-权限绑定逻辑
return Result.success();
}
@GetMapping("/user/perms")
@SaCheckLogin
public Result<List<String>> getCurrentUserPerms() {
return Result.success(StpUtil.getPermissionList());
}
}
六、最佳实践建议
-
权限粒度控制
- 接口级别:使用
@SaCheckPermission
- 模块级别:使用
@SaCheckRole
- 开放接口:通过白名单配置
- 接口级别:使用
-
性能优化
- 使用二级缓存(Redis + 本地缓存)
- 批量查询用户权限
- 启用 SaToken 的注解缓存
-
安全增强
sa-token: token-style: random-64 # 使用随机token jwt-secret-key: ${SA_JWT_SECRET} # 从环境变量读取 token-prefix: "Bearer " # 符合OAuth2规范
-
前端配合
- 动态路由:根据权限列表过滤前端路由
- 按钮级控制:通过
v-if="hasPerm('user:add')"
实现
七、常见问题解决方案
Q1 新权限未及时生效?
- 检查缓存策略
- 调用
StpUtil.getPermissionList(userId, true)
强制刷新
Q2 超级管理员权限处理?
@SaCheckPermission(
value = {"user:delete", "admin:full"},
mode = SaMode.OR
)
Q3 接口未配置鉴权?
- 开启开发环境鉴权检查
- 使用 AOP 扫描所有接口并告警
@Aspect
@Component
public class AuthCheckAspect {
@Around("@within(org.springframework.web.bind.annotation.RestController)")
public Object checkAuthAnnotation(ProceedingJoinPoint joinPoint) {
Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
if (!hasAuthAnnotation(method)) {
log.warn("接口未配置权限注解: {}", method.toString());
}
return joinPoint.proceed();
}
}
八、总结
通过 SaToken 与 RBAC 模型的深度整合,我们实现了:
- 灵活的角色权限管理:通过可视化界面动态配置权限
- 细粒度的接口控制:精确到单个接口的操作权限
- 高性能的鉴权方案:多级缓存保证系统吞吐量
- 安全的访问控制:多重校验机制保障系统安全
实现之后也可以轻易的给用户进行权限的分配,直接给角色勾选可以访问的接口即可 也可以设置好角色,给用户分配角色