权限概念介绍
权限:权利(能做的)和限制(不能做的),在权限范围内做好自己的事情
认证: 验证用户名密码是否正确的过程
授权: 对用户所能访问的资源进行控制(动态显示菜单、URL 级别的权限控制)
实现权限系统的原因
首先系统需要进行登陆才能访问
其次不同登陆用户要有不同的权利,而且要有不同的菜单(例如财务经理针对系统中财务相关模块进行操作,人事经理针对系统中人事模块进行操作)
权限控制基本原理
ACL(Access Control Lists)
ACL 是最早也是最基本的一种访问控制机制,它的原理非常简单:每一项资源,都配有一个列表,这个列表记录的就是哪些用户可以对这项资源执行 CRUD 中的哪些操作。当系统试图访问这项资源时,会首先检查这个列表中是否有关于当前用户的访问权限,从而确定当前用户可否执行相应的操作。总得来说,ACL 是一种面向资源的访问控制模型,它的机制是围绕“资源”展开的。
RBAC(Role-Based Access Control)
RBAC 是基于角色的访问控制,通过用户的角色来确定用户能否针对某项资源进行某项操作。RBAC 相对于 ACL 最大的优势就是它简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来,而用户与权限变成了间接关联。RBAC 模型使得访问控制,特别是对用户的授权管理变得非常简单和易于维护,因此有广泛的应用。
规则一:每个登陆的用户,可以有多个角色
规则二:每个角色又可以拥有多个权限(包含菜单和资源)
ACL:用户 -> 权限
RBAC:用户 -> 角色 -> 权限
权限系统经典五张表
用户表、角色表、权限表、用户角色中间表、角色权限中间表
权限模块功能分析
权限模块主要细分为角色模块、菜单模块、资源模块,将针对细分的三个模块进行具体功能实现,同时会完成用户登陆、用户关联角色及动态菜单显示
权限模块管理
实现以下功能
- 角色列表 & 条件查询(角色模块)
- 分配菜单(角色模块)
- 删除角色(角色模块)
- 菜单列表查询(菜单模块)
- 查询菜单信息回显(菜单模块)
- 资源分页&多条件查询(资源模块)
- 用户登陆(用户模块)
- 动态菜单展示(权限模块)
- 用户关联角色(用户模块)
权限模块表设计
数据库表
-
user
用户表 -
roles
角色表 -
menu
菜单表 -
resource
资源表 -
resource_category
资源分类表 -
user_role_relation
用户角色中间表 -
role_menu_relation
角色菜单中间表 -
role_resource_relation
角色资源中间表
表关系介绍
ER 图
多个用户对多个角色
权限表由菜单表和资源表组成;菜单表和资源表对权限表进行了细粒度划分
多个角色对多个权限表;用户表和权限表没有直接关联,是通过角色表进行了间接关联
一个资源分类表对多个资源表
数据实体描述
角色表
CREATE TABLE `roles` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '角色id',
`code` VARCHAR(100) NOT NULL COMMENT '角色code',
`name` VARCHAR(200) DEFAULT NULL COMMENT '角色名称',
`description` VARCHAR(500) DEFAULT NULL COMMENT '简介',
`created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
`updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
菜单表
CREATE TABLE `menu` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`parent_id` INT(11) NOT NULL COMMENT '父菜单id',
`href` VARCHAR(200) DEFAULT NULL COMMENT '菜单路径',
`icon` VARCHAR(200) DEFAULT NULL COMMENT '菜单图标',
`name` VARCHAR(200) DEFAULT NULL COMMENT '菜单名称',
`description` VARCHAR(500) DEFAULT NULL COMMENT '描述',
`order_num` INT(11) DEFAULT NULL COMMENT '排序号',
`shown` TINYINT(2) DEFAULT '1' COMMENT '是否展示',
`level` INT(11) NOT NULL COMMENT '菜单层级,从0开始',
`created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
`updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COMMENT='菜单表';
资源表
CREATE TABLE `resource` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '资源id',
`name` VARCHAR(200) NOT NULL COMMENT '资源名称',
`url` VARCHAR(200) NOT NULL COMMENT '资源url',
`category_id` INT(11) DEFAULT NULL COMMENT '分类id',
`description` VARCHAR(500) DEFAULT NULL COMMENT '简介',
`created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
`updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
PRIMARY KEY (`id`),
KEY `idx_category_id` (`category_id`)
) ENGINE=INNODB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8mb4 COMMENT='资源表';
资源分类表
CREATE TABLE `resource_category` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(200) DEFAULT NULL COMMENT '分类名称',
`sort` INT(4) DEFAULT NULL COMMENT '排序',
`created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
`updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='资源分类表';
用户角色中间表
CREATE TABLE `user_role_relation` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '关系id',
`user_id` INT(11) NOT NULL COMMENT '用户id',
`role_id` INT(11) NOT NULL COMMENT '角色id',
`created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
`updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COMMENT='用户和角色关系表';
角色菜单中间表
CREATE TABLE `role_menu_relation` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`menu_id` INT(11) NOT NULL COMMENT '菜单id',
`role_id` INT(11) NOT NULL COMMENT '角色id',
`created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
`updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=302 DEFAULT CHARSET=utf8mb4 COMMENT='角色和菜单关系表';
角色资源中间表
CREATE TABLE `role_resource_relation` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '关系id',
`resource_id` INT(11) NOT NULL COMMENT '资源id',
`role_id` INT(11) NOT NULL COMMENT '角色id',
`created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`created_by` VARCHAR(100) NOT NULL COMMENT '创建人',
`updated_by` VARCHAR(100) NOT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=105 DEFAULT CHARSET=utf8mb4 COMMENT='角色和资源关系表';
权限管理 - 角色模块接口实现
角色列表查询 & 条件查询
需求分析
点击角色列表按钮进行角色列表展示
查看接口文档,进行编码
实体类 Role
public class Role {
private Integer id;
private String code;
private String name;
private String description;
private Date createdTime;
private Date updatedTime;
private String createdBy;
private String updatedBy;
// getter setter toString ...
}
Dao 层 RoleMapper
public interface RoleMapper {
List<Role> findAllRole(Role role);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.renda.dao.RoleMapper">
<select id="findAllRole" parameterType="Role" resultType="Role">
select * from roles
<where>
<if test="name != null and name != '' ">
and name = #{name}
</if>
</where>
</select>
</mapper>
Service 层 RoleService
public interface RoleService {
List<Role> findAllRole(Role role);
}
@Service
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Override
public List<Role> findAllRole(Role role) {
return roleMapper.findAllRole(role);
}
}
Web 层 RoleController
@RestController
@RequestMapping("/role")
public class RoleController {
@Autowired
private RoleService roleService;
@RequestMapping("/findAllRole")
public ResponseResult findAllRole(@RequestBody Role role) {
List<Role> roleList = roleService.findAllRole(role);
return new ResponseResult(true, 200, "查询所有角色成功", roleList);
}
}
Postman 测试接口
添加 & 修改角色
需求分析
新建:点击提交按钮,将页面内容保存到数据库
修改:点击编辑按钮,由前端实现数据回显,在回显页面进行数据修改,将修改后值更新到数据库中
查看接口文档,进行编码
Dao 层 RoleMapper
void saveRole(Role role);
void updateRole(Role role);
<!-- 新增角色 -->
<insert id="saveRole" parameterType="Role">
insert into roles (`name`, `code`, `description`, `created_time`, `updated_time`, `created_by`, `updated_by`)
values (#{name}, #{code}, #{description}, #{createdTime}, #{updatedTime}, #{createdBy}, #{updatedBy})
</insert>
<!-- 更新角色 -->
<update id="updateRole" parameterType="Role">
update roles
<trim prefix="set" suffixOverrides=",">
<if test="name != null and name != ''">
`name` = #{name},
</if>
<if test="code != null and code != ''">
`code` = #{code},
</if>
<if test="description != null and description != ''">
`description` = #{description},
</if>
<if test=" updatedTime != null">
`updated_time` = #{updatedTime},
</if>
<if test="updatedBy != null and updatedBy != ''">
`updated_by` = #{updatedBy}
</if>
</trim>
<where>
<if test="id != null and id != ''">
`id` = #{id}
</if>
</where>
</update>
Service 层 RoleService
void saveRole(Role role);
void updateRole(Role role);
@Override
public void saveRole(Role role) {
// 封装数据
Date date = new Date();
role.setCreatedTime(date);
role.setUpdatedTime(date);
if (role.getCreatedBy() == null || "".equals(role.getCreatedBy())) {
role.setCreatedBy("system");
role.setUpdatedBy("system");
}
// 调用 mapper 方法
roleMapper.saveRole(role);
}
@Override
public void updateRole(Role role) {
// 封装数据
role.setUpdatedTime(new Date());
if (role.getUpdatedBy() == null || "".equals(role.getUpdatedBy())) {
role.setUpdatedBy("system");
}
// 调用 mapper 方法
roleMapper.updateRole(role);
}
Web 层 RoleController
@RequestMapping("/saveOrUpdateRole")
public ResponseResult saveOrUpdateRole(@RequestBody Role role) {
if (role.getId() == null) {
// 添加
roleService.saveRole(role);
return new ResponseResult(true, 200, "添加角色成功", null);
} else {
// 更新
roleService.updateRole(role);
return new ResponseResult(true, 200, "更新角色成功", null);
}
}
Postman 测试接口
分配菜单
需求分析
点击分配菜单,回显可选择菜单信息,并回显选中状态;点击保存按钮为角色保存所选择的菜单信息
查询所有菜单列表
实体类 Menu
public class Menu {
// 主键 id
private Integer id;
// 父菜单 id
private int parentId;
// 菜单路径
private String href;
// 菜单图标
private String icon;
// 菜单名称
private String name;
// 描述
private String description;
// 排序号
private int orderNum;
// 是否展示
private int shown;
// 菜单层级,从 0 开始
private int level;
// 创建时间
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
// 更新时间
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updatedTime;
// 创建人
private String createdBy;
// 更新人
private String updatedBy;
// 声明集合:当前父级菜单所关联的子级菜单
private List<Menu> subMenuList;
// getter setter toString ...
}
Dao 层 MenuMapper
public interface MenuMapper {
List<Menu> findSubMenuListByPid(int pid);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.renda.dao.MenuMapper">
<resultMap id="menuResult" type="Menu">
<id column="id" property="id"/>
<result column="href" property="href"/>
<result column="icon" property=