1. 简介
在绝大部分的微服务平台中,都会涉及到账号表(account)、用户表(user)、用户组表(user_group)、角色表(role) 和权限表(permission)等的通用数据表,然而这几张表之间又会存在一些非常通用的关系以及相同的处理方式。本文将基于角色访问控制(Role-Based Access Control)来设计一个通用“用户角色权限”平台,该设计可以非常好地被移植至各类微服务项目中。
RBAC(Role-Based Access Control)
RBAC(基于角色的访问控制)是指用户通过角色与权限进行关联。即一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户—角色—权限”的授权模型。在这种模型中,用户与角色之间、角色与权限之间,一般者是多对多的关系。
2. 用户中心—精简版
本节主要介绍一个能在生产环境使用的用户角色权限管理平台。
2.1 账号表(account)
在我们的系统中,会有各种各样的登录方式,如手机号、邮箱地址、身份证号码和微信登录等。因此该表主要是用来记录每一种登录方式的信息,但不包含密码信息,因为各种登录方式都会使用同一个密码。每一条记录都会关联到唯一的一条用户记录。
CREATE TABLE `account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '账号ID',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`open_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '登录账号,如手机号等',
`category` tinyint(1) NULL DEFAULT NULL COMMENT '账号类别',
`created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`creator` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`edited` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`editor` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` double(1, 0) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_member_id`(`user_id`) USING BTREE COMMENT '普通索引',
CONSTRAINT `account_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '账号' ROW_FORMAT = Dynamic;
2.2 用户表(user)
主要是用来记录用户的基本信息和密码信息。其中禁用状态(state)主要是在后台管理控制非法用户使用系统;密码加盐(salt)则是用于给每个用户的登录密码加一把唯一的锁,即使公司加密公钥泄露后,也不会导致全部用户的密码泄露。
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`state` tinyint(1) NULL DEFAULT NULL COMMENT '用户状态:0=正常,1=禁用',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
`head_img_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像图片地址',
`mobile` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号码',
`salt` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码加盐',
`password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '登录密码',
`created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`creator` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`edited` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`editor` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` tinyint(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户' ROW_FORMAT = Dynamic;
用户账号表(account)和用户表(user)组合在一起,就完成了用户账号模块的设计。如果还想再细分,则可以将两张表拆为以下三张表:
- 用户信息表(user):只存储用户基本信息(不包括密码)
- 账号表(account):只存储账号相关信息(如密码、注册来源、注册 IP,但不包括登录账号)
- 登录账号表(login_account):用来存储每一种登录方式的信息(不包括密码信息)
当然,如果用户信息的字段太多,则可以适当拆分为多张不同领域的用户信息表,这里不再介绍。
2.3 权限表(permission)
有了用户之后,我们希望不同的用户能操作和查看不同的功能(如页面、菜单和按钮等)。因此需要定义一张表来存储权限相关的信息。包括权限之前还有父子关系,分配了父级后,应该拥有所有的子级权限。同时权限的信息也会分配至前端页面来控制,因此需要提供一个唯一标识(code),有人会问 id 不行吗?当然可以,只是我们的 ID 是自动生成,每个环境都不一样,重新生成后也不一样,因此才单独使用了一个字段来标识。
CREATE TABLE `permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`parent_id` bigint(20) NULL DEFAULT NULL COMMENT '所属父级权限ID',
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限唯一CODE代码',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称',
`intro` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限介绍',
`category` tinyint(1) NULL DEFAULT NULL COMMENT '权限类别',
`uri` bigint(20) NULL DEFAULT NULL COMMENT 'URL规则',
`created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`creator` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`edited` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`editor` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` tinyint(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `parent_id`(`parent_id`) USING BTREE COMMENT '父级权限ID',
INDEX `code`(`code`) USING BTREE COMMENT '权限CODE代码'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限' ROW_FORMAT = Dynamic;
其实,再加一张 用户权限表(user_permission) 即可组成一个用户权限中心了,但这样做是不好的。比如我们设想:有 200 个权限点,1 亿个用户,每个人平均配置 100 个权限,则 user_permission 表将会有 100 亿条记录,而且每当我们新增 1 个权限点时,可能需要添加上亿条记录。简而言之会有以下弊端:
- 中间数据量庞大
- 新增或编辑时,操作数据量的数据量也是非常庞大
- 维护起来也很麻烦
因此,我们会引入一张角色表(role)来解决问题。
2.4 角色表(role)
为了解决维护起来方便,我们会对权限表中的记录进行分组,将相关的一些权限分配为同一组,称之为角色。角色表的作用是为了将零散的权限进行聚合,然后方便对相关的一组进行统一处理(即小范围批量处理)。该表的增加可谓是大大减少了上述维护困难的问题。
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`parent_id` bigint(20) NULL DEFAULT NULL COMMENT '所属父级角色ID',
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色唯一CODE代码',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
`intro` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色介绍',
`created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`creator` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`edited` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`editor` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` tinyint(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `parent_id`(`parent_id`) USING BTREE COMMENT '父级权限ID',
INDEX `code`(`code`) USING BTREE COMMENT '权限CODE代码'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色' ROW_FORMAT = Dynamic;
2.5 用户—角色表(user_role)
该表主要是用来存储每个用户拥有哪些角色。一般情况,每个用户只会有几个角色,因此数据量从 100 亿变成 10 亿或更少。
CREATE TABLE `user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID',
`created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`creator` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`edited` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`editor` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` tinyint(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `member_id`(`user_id`) USING BTREE COMMENT '用户ID',
INDEX `role_id`(`role_id`) USING BTREE COMMENT '角色ID',
CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色' ROW_FORMAT = Dynamic;
2.6 角色 - 权限表(role_permission)
该表则是用来定义每个角色组中有哪些权限。该表的数量则更少(基本都在 1 万条以内)。
CREATE TABLE `role_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID',
`permission_id` bigint(20) NULL DEFAULT NULL COMMENT '权限ID',
`created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`creator` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`edited` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`editor` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` tinyint(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `role_id`(`role_id`) USING BTREE COMMENT '角色ID',
INDEX `permission_id`(`permission_id`) USING BTREE COMMENT '权限ID',
CONSTRAINT `role_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `role_permission_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色权限' ROW_FORMAT = Dynamic;
总结一下,本节所说的精简版代表的是,能在线上运行的表设计(4 张实体表和 2 张中间表),虽然使用 account、user、permission、user_permission 也能用,但基本没人会这样来用的,因为对后期维护起来很麻烦,数据量也很庞大。
- 账号表(account):记录登录账号信息
- 用户表(user):记录用户基本信息和密码
- 权限表(permission):记录权限信息
- 角色表(role):记录角色信息,即定义权限组
- 用户—角色表(user_role):记录每个用户拥有哪些角色信息
- 角色—权限表(role_permission):记录每个角色拥有哪些权限信息
3. 用户中心—完整版
本节是在上述 6 张表的基础上进行再次优化设计延伸。
3.1 用户组表(user_group)
上述虽然增加了角色表(role)后,把数据量从 100 亿降低至 10 亿,但 10 倍的数据量依然还是很多。而且大部分的用户(主体用户。如学生系统,学生就是主体)都会分配相同的角色组。用户组和角色组的区别:
- 角色组(role):解决的是权限的分组,减少了权限的重复分配
- 用户组(user_group):解决的是用户的分组,减少了用户的重复授权
CREATE TABLE `user_group` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`parent_id` bigint(20) NULL DEFAULT NULL COMMENT '所属父级用户组ID',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户组名称',
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户组CODE唯一代码',
`intro` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户组介绍',
`created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`creator` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`edited` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`editor` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` tinyint(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `parent_id`(`parent_id`) USING BTREE COMMENT '父级用户组ID',
INDEX `code`(`code`) USING BTREE COMMENT '用户组CODE代码'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户组' ROW_FORMAT = Dynamic;
3.2 用户组—用户表(user_group_user)
该表用来记录每个用户组下有哪些用户。
CREATE TABLE `user_group_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID说',
`user_group_id` bigint(20) NULL DEFAULT NULL COMMENT '用户组ID',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`creator` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`edited` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`editor` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` tinyint(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `member_group_id`(`user_group_id`) USING BTREE COMMENT '用户组ID',
INDEX `member_id`(`user_id`) USING BTREE COMMENT '用户ID',
CONSTRAINT `user_group_user_ibfk_1` FOREIGN KEY (`user_group_id`) REFERENCES `user_group` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `user_group_user_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户组成员' ROW_FORMAT = Dynamic;
3.3 用户组—角色表(user_group_role)
该表用来记录每个用户组下拥有哪些用户角色。
CREATE TABLE `user_group_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_group_id` bigint(20) NULL DEFAULT NULL COMMENT '用户组ID',
`role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID',
`created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`creator` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`edited` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`editor` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` tinyint(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `member_group_id`(`user_group_id`) USING BTREE COMMENT '用户组ID',
INDEX `role_id`(`role_id`) USING BTREE COMMENT '角色ID',
CONSTRAINT `user_group_role_ibfk_1` FOREIGN KEY (`user_group_id`) REFERENCES `user_group` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `user_group_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户组角色' ROW_FORMAT = Dynamic;
每个系统主体用户,基本都占用了所有用户的 90% 以上(既包含用户,又包含商家的系统,用户和商家同时都是主题用户)。因此,每个用户注册时,基本只需要分配一条所属的用户组,即可完成角色权限的配置。这样处理后,数据量将从 10 亿下降至 1 亿多。同时也减少了用户注册时的需批量写入数量。