关于 SpringSecurity 之手的文本 [VIII]。
RBAC(Role-Based Access Control)
,基于角色的访问控制。通过用户关联角色,角色关联权限,来间接的为用户赋予权限。
一、RBAC介绍
RBAC(Role-Based Access Control),即基于角色的访问控制模型。
1.1 基本概念
- 角色(Role):
- 角色是 RBAC 模型的核心概念之一。它是一组权限的集合,代表了特定的工作职责或功能。例如,可以有 “管理员”“普通用户”“财务人员” 等不同的角色。
- 角色将用户与权限进行解耦,使得权限管理更加清晰和易于维护。
- 用户(User)
- 用户是系统的使用者。在 RBAC 模型中,用户通过被分配到不同的角色来获得相应的权限。
- 一个用户可以被分配多个角色,从而拥有多个角色所对应的权限。
- 权限(Permission)
- 权限定义了对系统资源的具体操作许可。例如,可以是对某个文件的读取权限、对数据库表的修改权限等。
- 权限通常与系统中的具体资源和操作相关联。
1.2 基本工作原理
-
用户分配角色:
- 系统管理员将不同的角色分配给用户。用户在登录系统后,根据其被分配的角色来确定拥有的权限。
- 例如,新员工入职时,管理员可以根据其工作职责为其分配 “普通员工” 角色。
-
角色关联权限:
- 管理员将各种权限分配给不同的角色。每个角色拥有一组特定的权限集合。
- 比如,“管理员” 角色可能拥有对系统所有资源的读写和管理权限,而 “普通用户” 角色可能只有对部分资源的读取权限。
-
权限控制:
- 当用户在系统中进行操作时,系统会根据用户的角色来判断其是否具有执行该操作的权限。
- 如果用户具有相应的权限,则操作被允许;否则,操作被拒绝。
1.3 数据库表
create database if not exists `rj-security-db`;
use `rj-security-db`;
-- 用户表
DROP TABLE IF EXISTS `tb_sys_user`;
CREATE TABLE `tb_sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`nick_name` varchar(150) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
`password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name`(`name` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户管理' ROW_FORMAT = DYNAMIC;
INSERT INTO `tb_sys_user` VALUES (4, 'zhangsan', '张三', '$2a$10$dCr8Skk7kLa2kNCms.23aeiYI2RS2vrdoSae6Jz3.0w.YCiu9lmT2', NULL);
INSERT INTO `tb_sys_user` VALUES (5, 'jack', '杰克', '$2a$10$dCr8Skk7kLa2kNCms.23aeiYI2RS2vrdoSae6Jz3.0w.YCiu9lmT2', NULL);
-- 角色表
DROP TABLE IF EXISTS `tb_sys_role`;
CREATE TABLE `tb_sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色管理' ROW_FORMAT = DYNAMIC;
INSERT INTO `tb_sys_role` VALUES (10, '超级管理员', NULL);
INSERT INTO `tb_sys_role` VALUES (11, '技术经理', NULL);
INSERT INTO `tb_sys_role` VALUES (12, '财务总监', NULL);
INSERT INTO `tb_sys_role` VALUES (13, '研发工程师', NULL);
INSERT INTO `tb_sys_role` VALUES (14, '人事专员', NULL);
INSERT INTO `tb_sys_role` VALUES (15, '产品经理', NULL);
-- 权限表【菜单表】
DROP TABLE IF EXISTS `tb_sys_menu`;
CREATE TABLE `tb_sys_menu` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名称',
`parent_id` bigint NULL DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`url` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单URL,类型:1.普通页面(如用户管理, /sys/user)2.嵌套完整外部页面,以http(s)开头的链接 ',
`perms` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:sys:user:add,sys:user:edit)',
`type` int NULL DEFAULT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单图标',
`order_num` int NULL DEFAULT NULL COMMENT '排序',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 64 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单管理' ROW_FORMAT = DYNAMIC;
INSERT INTO `tb_sys_menu` VALUES (64, '用户管理', 0, '/user/list', 'sys:user:list', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (65, '用户添加', 64, '/user/add', 'sys:user:add', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (66, '用户删除', 64, '/user/delete', 'sys:user:delete', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (67, '用户更新', 64, '/user/update', 'sys:user:update', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (68, '商品管理', 0, '/goods/list', 'sys:goods:list', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (69, '商品添加', 68, '/goods/add', 'sys:goods:add', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (70, '商品删除', 68, '/goods/delete', 'sys:goods:delete', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (71, '商品更新', 68, '/goods/update', 'sys:goods:update', NULL, NULL, NULL, NULL);
-- 用户-角色-中间表
DROP TABLE IF EXISTS `tb_sys_user_role`;
CREATE TABLE `tb_sys_user_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`user_id` bigint NULL DEFAULT NULL COMMENT '用户ID',
`role_id` bigint NULL DEFAULT NULL COMMENT '角色ID',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色' ROW_FORMAT = DYNAMIC;
INSERT INTO `tb_sys_user_role` VALUES (5, 4, 10, '2025-05-18 11:16:27');
INSERT INTO `tb_sys_user_role` VALUES (6, 4, 11, '2025-05-18 11:16:35');
INSERT INTO `tb_sys_user_role` VALUES (7, 5, 15, '2025-05-18 11:16:37');
INSERT INTO `tb_sys_user_role` VALUES (8, 5, 13, '2024-05-18 11:16:39');
INSERT INTO `tb_sys_user_role` VALUES (9, 5, 14, '2024-05-18 11:16:41');
-- 角色-权限【菜单】中间表
DROP TABLE IF EXISTS `tb_sys_role_menu`;
CREATE TABLE `tb_sys_role_menu` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`role_id` bigint NULL DEFAULT NULL COMMENT '角色ID',
`menu_id` bigint NULL DEFAULT NULL COMMENT '菜单ID',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 632 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色菜单' ROW_FORMAT = DYNAMIC;
INSERT INTO `tb_sys_role_menu` VALUES (632, 10, 64, '2023-05-18 11:17:50');
INSERT INTO `tb_sys_role_menu` VALUES (633, 10, 65, '2023-10-18 11:18:02');
INSERT INTO `tb_sys_role_menu` VALUES (634, 10, 66, '2024-09-18 11:18:18');
INSERT INTO `tb_sys_role_menu` VALUES (635, 10, 67, '2029-05-18 11:18:27');
INSERT INTO `tb_sys_role_menu` VALUES (636, 10, 68, '2024-05-23 11:18:37');
INSERT INTO `tb_sys_role_menu` VALUES (637, 10, 69, '2025-06-18 11:18:49');
INSERT INTO `tb_sys_role_menu` VALUES (638, 10, 70, '2033-09-18 11:19:01');
INSERT INTO `tb_sys_role_menu` VALUES (639, 11, 64, '2027-05-18 11:19:23');
INSERT INTO `tb_sys_role_menu` VALUES (640, 11, 65, '2036-06-18 11:19:39');
INSERT INTO `tb_sys_role_menu` VALUES (641, 15, 68, '2024-12-18 11:20:00');
INSERT INTO `tb_sys_role_menu` VALUES (642, 15, 71, '2022-05-18 11:20:14');
INSERT INTO `tb_sys_role_menu` VALUES (643, 14, 64, '2024-06-21 11:20:50');
INSERT INTO `tb_sys_role_menu` VALUES (644, 14, 65, '2028-05-18 11:21:10');
RBAC跟语言和框架没有啥关系,纯纯一种技术解决方案而已, 不同的安全框架都有不同的实现. 上述五张表,结合自己的实际需求,自行修改,这里我们只是为了做测试而已.
二、spring security 授权处理
2.1 授权的通俗解释
- 张三是一个普通的个人, 这相当于普通用户
- 村长、县长、市长这些相当于角色
- 简单理解, 村长只能管理一个村子, 县长能管理一个县, 市长管理一个市…, 这是权限
张三可以成为一个【村长】, 可以【管理一个村子】, 从大的粒度考量, 因为张三是村长,所以能管理一个村子, 并不是它叫张三, 所以这里可以抽象出来【基于角色信息的权限控制】.
现在张三是一个村长了, 村长可以管理村里的纠纷, 管理村里的财政, 不管是管理纠纷还是村里的财政这些都是村长这个角色对应的一条一条的权限.
以上就是用户、角色、权限的通俗解释,当然它们用户-角色-权限【菜单】之间的关系都是多对多的.
2.2 授权处理
在接口UserDetails 当中
public interface UserDetails extends Serializable {
// 权限集合
Collection<? extends GrantedAuthority> getAuthorities();
}
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
通过以上的接口定义,可以看出来,所谓的权限就是一个字符串而已. 我们常用的一个实现类就是: SimpleGrantedAuthority, 可以查看一下它的类定义
public final class SimpleGrantedAuthority implements GrantedAuthority {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final String role;
public SimpleGrantedAuthority(String role) {
Assert.hasText(role, "A granted authority textual representation is required");
this.role = role;
}
@Override
public String getAuthority() {
return this.role;
}
// 略...
}
通过调用SimpleGrantedAuthority构建方法,可以将一个普通的字符串转换为SimpleGrantedAuthority对象被spring security所识别.
另外一个就是SimpleGrantedAuthority是没有无参构造的,这一点再执行它的对象的序列化、反序列化打时候要特别注意.
2.3 权限字符串
基于角色的授权规则包括 ROLE_ 作为前缀, 也可以通过如下的方式进行修改.
public final class GrantedAuthorityDefaults {
private final String rolePrefix;
public GrantedAuthorityDefaults(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
/**
* The default prefix used with role based authorization. Default is "ROLE_".
* @return the default role prefix
*/
public String getRolePrefix() {
return this.rolePrefix;
}
}
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("RJ_");
}
使用静态方法公开GrantedAuthorityDefaults,以确保 Spring 在初始化 Spring Security 的方法安全性@Configuration类之前发布它
权限字符串分隔符一般以冒号者分隔, 譬如说: sys:user:create, 表示系统模块下的用户服务的创建权限.当然这并不是必须的,一切结合公司规则为准则.
2.4 添加权限字符串
在UserDetailsServiceImpl中,也就是UserDetailsService的实现类,此处我们之前传递的是个空集合,现在传递一些权限字符串.
修改代码如下所示:
修改TokenAuthenticationFilter即token的校验过滤器
2.5 开启授权处理
前置条件:
授权的前端是当前用户必须经过认证
spring security当中,会使用默认的FilterSecrityInterceptor
来进行权限的校验。
- 从FitlerSecurityInterceptor当中会从
SecurityContextHolder
获取其中的Authentication
, 然后获取权限信息,当前用户是否人的了该资源的访问的权限. - 我们需要把当前用户的具体权限也存储到
Authentication
当点,然后设置我们的资源所需要的权限即可. - 使用时候,配置类配置开启注解:
@EnableMethodSecurity
-
@EnableMethodSecurity
, 开启基于方法的授权
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MethodSecuritySelector.class)
public @interface EnableMethodSecurity {
}
这里有两个特别重要的属性:
- boolean prePostEnabled() default true;表示注解@PreAuthorize、 @PostAuthorize、 @PreFilter和 @PostFilter是否开启,默认就是【开启】状态
- boolean securedEnabled() default false; 表示注解@secured是否开启, true,表示开启, false,关闭
2.6 编写测试接口
在HomeController当中, 添加如下测试代码
// 测试权限字符串
// 表示只有当前用户有: ROLE_user这个角色才测出访问这个接口
@Secured("ROLE_user")
@GetMapping("/api/pub/v1/list")
public Result menu(){
List<String> menuList = new ArrayList<>();
menuList.add("商品管理");
menuList.add("用户管理");
menuList.add("查看商品");
menuList.add("查看客户信息");
return Result.success(0, "获取列表", menuList);
}
@Secured(“ROLE_user”), 于角色的权限控制注解。可以指定一个或多个角色,只有具有这些角色的用户才能执行被注解的方法。可以指定一个或者多个角色的字符串,写法简单,但是功能也十分简单,以上表示,当前认证的用户如果拥有ROLE_user这个角色, 那么接口可以访问,如果当前认证的用户没有ROLE_user角色,那么无法访问接口,会抛出异常.
2.7 前端测试工程添加代码并验证
// 测试接口,主要验证权限字符串
export const getMneuListTest = () => $http({url: '/list', method: 'get'})
const m1 = async (
上一篇:
Python Coding Series - Python 访问者模式:为对象结构添加新功能的艺术 - 1. 背景
下一篇:
第一次认识产品经理
推荐阅读
-
关于 SpringSecurity 之手的文本 [VIII]。
-
一篇关于 SpringSecurity 之手的文章 [IX] --首先,介绍 Redis
-
关于 SpringSecurity 之手的文本 [3]。
-
关于画布和 css 中文本的外笔画和内笔画
-
我的 PaddlePaddle 学习之旅笔记 VIII - 场景文本识别
-
关于用于冷发动机逆向分析的文本提取的初步想法
-
回顾2015年的HPV病毒基因研究:205种类型的发现,通过Python抓取获取了179个约8KB碱基序列。基于GeneBank或类似ID的批量核酸序列下载实用脚本" 该脚本说明: - 介绍了一个在2015年针对HPV病毒基因进行的研究,其中已确认有205种类型的基因,并通过Python编程从GeneBank获取了179个大约8KB的DNA序列片段。 - 提供了一份简单易用的Python脚本,用户只需提供一行一个含有相关ID(如GeneBank ID)的文本文件作为输入。 - 脚本功能包括随机暂停以避免过于频繁的请求,适用于快速、大批量地下载指定ID下的核酸序列信息。 - 还提及了关于HBV病毒提取方法的相关文献背景,同时也揭示了当年作者使用Python编写此类工具的经历。