1、权限控制
如果没有权限控制,系统的功能完全不设防,全部暴露在所有用户面前。用户登录以后可以使用系统中的所有功能。这是实际运行中不能接受的,所以权限控制系统的目标就是管理用户行为,保护系统功能。
1.1、 定义资源
资源就是系统中需要保护起来的功能。具体形式很多:URL 地址、controller方法、service 方法、页面元素等等都可以定义为资源使用权限控制系统保护起来。
1.2、 创建权限
一个功能复杂的项目会包含很多具体资源,成千上万都有可能。这么多资源逐个进行操作太麻烦了。为了简化操作,可以将相关的几个资源封装到一起,打包成一个“权限”同时分配给有需要的人。
1.3、 创建角色
对于一个庞大系统来说,一方面需要保护的资源非常多,另一方面操作系统的人也非常多。把资源打包为权限是对操作的简化,同样把用户划分为不同角色也是对操作的简化。否则直接针对一个个用户进行管理就会很繁琐。所以角色就是用户的分组、分类。先给角色分配权限,然后再把角色分配给用户,用户以这个角色的身份操作系统就享有角色对应的权限了。
1.4 管理用户
系统中的用户其实是人操作系统时用来登录系统的账号、密码。
1.5、 建立关联关系
权限→资源:单向多对多
-
Java 类之间单向:从权限实体类可以获取到资源对象的集合,但是通过资源获取不到权限
-
数据库表之间多对多:
-
一个权限可以包含多个资源
-
一个资源可以被分配给多个不同权限
-
角色→权限:单向多对多
-
Java 类之间单向:从角色实体类可以获取到权限对象的集合,但是通过权限获取不到角色
-
数据库表之间多对多:
-
一个角色可以包含多个权限
-
一个权限可以被分配给多个不同角色
-
用户→角色:双向多对多
-
Java 类之间双向:可以通过用户获取它具备的角色,也可以看一个角色下包含哪些用户
-
数据库表之间:
-
一个角色可以包含多个用户
-
一个用户可以身兼数职
-
2、RBAC 权限模型
2.1 概念
鉴于权限控制的核心是用户通过角色与权限进行关联,所以前面描述的权限控制系统可以提炼为一个模型:RBAC(Role-Based Access Control,基于角色的访问控制)。在 RBAC 模型中,一个用户可以对应多个角色,一个角色拥有多个权限,权限具体定义用户可以做哪些事情。
2.2.1 RBAC0
最基本的 RBAC 模型,RBAC 模型的核心部分,后面三种升级版 RBAC 模型也都是建立在 RBAC0 的基础上。
2.2.2 RBAC1
在 RBAC0 的基础上增加了角色之间的继承关系。角色 A 继承角色 B 之后将具备 B 的权限再增加自己独有的其他权限。比如:付费会员角色继承普通会员角色,那么付费会员除了普通会员的权限外还具备浏览付费内容的权限
2.2.3 RBAC2
在 RBAC0 的基础上进一步增加了角色责任分离关系。责任分离关系包含静态责任分离和动态责任分离两部分。
-
静态责任分离:给用户分配角色时生效
-
互斥角色:权限上相互制约的两个或多个角色就是互斥角色。用户只能被分配到一组互斥角色中的一个角色。例如:一个用户不能既有会计师角色又有审计师角色。
-
基数约束:
-
一个角色对应的访问权限数量应该是受限的
-
一个角色中用户的数量应该是受限的
-
一个用户拥有的角色数量应该是受限的
-
-
先决条件角色:用户想拥有 A 角色就必须先拥有 B 角色,从而保证用户拥有 X 权限的前提是拥有 Y 权限。
-
-
动态责任分离:用户登录系统时生效
-
一个用户身兼数职,在特定场景下激活特定角色
-
2.2.4 RBAC3
RBAC3 是在 RBAC0 的基础上同时添加 RBAC2 和 RBAC3 的约束,最全面、最复杂
3、角色维护:
3.1、创建角色表
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80034
Source Host : localhost:3306
Source Schema : project_crowd
Target Server Type : MySQL
Target Server Version : 80034
File Encoding : 65001
Date: 01/11/2023 11:36:13
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` int NOT NULL AUTO_INCREMENT,
`name` char(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
3.2、使用插件逆向工程
这个就不说啦!
3.3、分页查询:
3.3.1、关键字查询
展示
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th width="30"><input type="checkbox"></th>
<th width="50">编号</th>
<th>名称</th>
<th width="100">操作</th>
</tr>
</thead>
<tbody id="rolePageBody">
</tbody>
<tfoot>
<tr>
<td colspan="6" align="center">
<div id="Pagination" class="pagination"><!-- 这里显示分页 --></div>
</td>
</tr>
</tfoot>
</table>
</div>
js
// 声明专门的函数显示确认模态框
function showConfirmModal(roleArray) {
// 打开模态框
$("#confirmModal").modal("show");
// 清除旧的数据
$("#roleNameDiv").empty();
// 在全局变量范围创建数组用来存放角色id
window.roleIdArray = [];
// 遍历roleArray数组
for(var i = 0; i < roleArray.length; i++) {
var role = roleArray[i];
var roleName = role.roleName;
$("#roleNameDiv").append(roleName+"<br/>");
var roleId = role.roleId;
// 调用数组对象的push()方法存入新元素
window.roleIdArray.push(roleId);
}
}
// 执行分页,生成页面效果,任何时候调用这个函数都会重新加载页面
function generatePage() {
// 1.获取分页数据
var pageInfo = getPageInfoRemote();
// 2.填充表格
fillTableBody(pageInfo);
}
// 远程访问服务器端程序获取pageInfo数据
function getPageInfoRemote() {
// 调用$.ajax()函数发送请求并接受$.ajax()函数的返回值
var ajaxResult = $.ajax({
"url": "role/get/page/info.json",
"type":"post",
"data": {
"pageNum": window.pageNum,
"pageSize": window.pageSize,
"keyword": window.keyword
},
"async":false,
"dataType":"json"
});
console.log(ajaxResult);
// 判断当前响应状态码是否为200
var statusCode = ajaxResult.status;
// 如果当前响应状态码不是200,说明发生了错误或其他意外情况,显示提示消息,让当前函数停止执行
if(statusCode != 200) {
layer.msg("失败!响应状态码="+statusCode+" 说明信息="+ajaxResult.statusText);
return null;
}
// 如果响应状态码是200,说明请求处理成功,获取pageInfo
var resultEntity = ajaxResult.responseJSON;
// 从resultEntity中获取result属性
var result = resultEntity.result;
// 判断result是否成功
if(result == "FAILED") {
layer.msg(resultEntity.message);
return null;
}
// 确认result为成功后获取pageInfo
var pageInfo = resultEntity.data;
// 返回pageInfo
return pageInfo;
}
// 填充表格
function fillTableBody(pageInfo) {
// 清除tbody中的旧的内容
$("#rolePageBody").empty();
// 这里清空是为了让没有搜索结果时不显示页码导航条
$("#Pagination").empty();
// 判断pageInfo对象是否有效
if(pageInfo == null || pageInfo == undefined || pageInfo.list == null || pageInfo.list.length == 0) {
$("#rolePageBody").append("<tr><td colspan='4' align='center'>抱歉!没有查询到您搜索的数据!</td></tr>");
return ;
}
// 使用pageInfo的list属性填充tbody
for(var i = 0; i < pageInfo.list.length; i++) {
var role = pageInfo.list[i];
var roleId = role.id;
var roleName = role.name;
var checkboxTd = "<td><input id='"+roleId+"' class='itemBox' type='checkbox'></td>";
var numberTd = "<td>"+(i+1)+"</td>";
var roleNameTd = "<td>"+roleName+"</td>";
var checkBtn = "<button type='button' class='btn btn-success btn-xs'><i class=' glyphicon glyphicon-check'></i></button>";
// 通过button标签的id属性(别的属性其实也可以)把roleId值传递到button按钮的单击响应函数中,在单击响应函数中使用this.id
var pencilBtn = "<button id='"+roleId+"' type='button' class='btn btn-primary btn-xs pencilBtn'><i class=' glyphicon glyphicon-pencil'></i></button>";
// 通过button标签的id属性(别的属性其实也可以)把roleId值传递到button按钮的单击响应函数中,在单击响应函数中使用this.id
var removeBtn = "<button id='"+roleId+"' type='button' class='btn btn-danger btn-xs removeBtn'><i class=' glyphicon glyphicon-remove'></i></button>";
var buttonTd = "<td>"+checkBtn+" "+pencilBtn+" "+removeBtn+"</td>";
var tr = "<tr>"+numberTd+checkboxTd+roleNameTd+buttonTd+"</tr>";
$("#rolePageBody").append(tr);
}
// 生成分页导航条
generateNavigator(pageInfo);
}
// 生成分页页码导航条
function generateNavigator(pageInfo) {
// 获取总记录数
var totalRecord = pageInfo.total;
// 声明相关属性
var properties = {
"num_edge_entries": 3,
"num_display_entries": 5,
"callback": paginationCallBack,
"items_per_page": pageInfo.pageSize,
"current_page": pageInfo.pageNum - 1,
"prev_text": "上一页",
"next_text": "下一页"
}
// 调用pagination()函数
$("#Pagination").pagination(totalRecord, properties);
}
// 翻页时的回调函数
function paginationCallBack(pageIndex, jQuery) {
// 修改window对象的pageNum属性
window.pageNum = pageIndex + 1;
// 调用分页函数
generatePage();
// 取消页码超链接的默认行为
return false;
}
展示调用的分页函数
$(function () {
//初始化数据
window.pageNum = 1;
window.pageSize = 5;
window.keyword = "";
//调用分页函数
generatePage();
$("#searchBtn").click(function () {
//获取关键字数据
window.keyword = $("#keywordInput").val();
//调用函数
generatePage();
});
})
3.3.2、添加角色
创建模态框jsp:默认是放在最后的位置
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<div id="addModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">创筹网系统弹窗</h4>
</div>
<div class="modal-body">
<form class="form-signin" role="form">
<div class="form-group has-success has-feedback">
<input
type="text" name="roleName"
class="form-control" id="inputSuccess4" placeholder="请输入角色名称" autofocus>
</div>
</form>
</div>
<div class="modal-footer">
<button id="saveRoleBtn" type="button" class="btn btn-primary"> 保存
</button>
</div>
</div>
</div>
</div>
点击添加展示模态框
//点击添加打开模态框
$("#showAddModalBtn").click(function () {
$("#addModal").modal("show");
});
前端操作:
//获取模态框中的数据,然后发送请求
$("#saveRoleBtn").click(function () {
let roleName = $.trim($("#addModal [name=roleName]").val());
// 发送异步请求
$.ajax({
"url": "role/save.json",
"type": "post",
"data": {
"name": roleName
},
"dataType":"json",
"success":function (response) {
let result = response.result;
if (result=="SUCCESS"){
layer.msg("操作成功!");
//重新加载分页
window.pageNum=999999;
generatePage();
}
if (result=="FAILED"){
layer.msg("操作失败!"+response.message)
}
},
"error":function (response) {
layer.msg(response.status+"" +response.statusText)
}
});
//关闭模态框
$("#addModal").modal("hide");
//清理模态框
$("#addModal [name=roleName]").val("")
});
controller
@ResponseBody
@RequestMapping(value = "/role/save.json")
public ResultEntity<String> saveRole(Role role){
roleService.saveRole(role);
return ResultEntity.successWithoutData();
}
service
/**
* @description: 添加角色
* @author: 斗痘侠
* @date: 2023/11/1 19:21
* @param: role
**/
@Override
public void saveRole(Role role) {
roleMapper.insertSelective(role);
}
3.3.3、修改角色
前端:
//给更新按钮绑定事件
$("#updateRoleBtn").click(function () {
if(confirm("确定更新嘛")){
//获取角色名称
let roleName = $("#editModal [name=roleName]").val();
$.ajax({
"url": "role/update.json",
"type":"post",
"data":{
"id":window.roleId,
"name":roleName
},
"dataType":"json",
"success": function (response) {
let result = response.result;
if (result == "SUCCESS") {
layer.msg("操作成功!");
//重新加载分页
generatePage();
}
if (result == "FAILED") {
layer.msg("操作失败!" + response.message)
}
},
"error": function (response) {
layer.msg(response.status + "" + response.statusText)
}
});
$("#editModal").modal("hide");
}
});
controller
@ResponseBody
@RequestMapping(value = "/role/update.json")
public ResultEntity<String> updateRole(Role role){
roleService.updateRole(role);
return ResultEntity.successWithoutData();
}
service
/**
* @description: 更新操作
* @author: 斗痘侠
* @date: 2023/11/2 8:15
* @param: role
**/
@Override
public void updateRole(Role role) {
roleMapper.updateByPrimaryKey(role);
}
3.3.4、删除角色:
模态框:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<div id="confirmModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">创筹网系统弹窗</h4>
</div>
<div class="modal-body">
<h4>请确认是否要删除下列角色:</h4>
<div id="roleNameDiv" style="text-align: center;"></div>
</div>
<div class="modal-footer">
<button id="removeRoleBtn" type="button" class="btn btn-primary">确认删除</button>
</div>
</div>
</div>
</div>
3.3.4.1、单选删除:
//点击模态框删除
$("#removeRoleBtn").click(function () {
var requestBody=JSON.stringify(window.roleIdArray);
$.ajax({
"url":"role/remove/by/role/id/array.json",
"type":"psot",
"data":requestBody,
"contentType":"application/json;charset=UTF-8",
"dataTpe":"json",
"success": function (response) {
let result = response.result;
if (result == "SUCCESS") {
layer.msg("操作成功!");
//重新加载分页
generatePage();
}
if (result == "FAILED") {
layer.msg("操作失败!" + response.message)
}
},
"error": function (response) {
layer.msg(response.status + "" + response.statusText)
}
});
//关闭模态框
$("#confirmModal").modal("hide");
});
//单挑删除
$("#rolePageBody").on("click", ".removeBtn", function () {
let roleName = $(this).parent().prev().text();
//创建rile对象
var roleArray=[{
roleId:this.id,
roleName:roleName
}];
showConfirmModal(roleArray);
});
3.3.4.2、多选删除:
//全选全不选
$("#summaryBox").click(function () {
//获取当前的状态
let checkedStatus = this.checked;
//改变属性值
$(".itemBox").prop("checked",checkedStatus);
});
$("#rolePageBody").on("click",".itemBox",function () {
//获取当前已经选中的itemBox的数量
let checkedBoxCount = $(".itemBox:checked").length;
//总的
let totalBoxCount = $(".itemBox").length;
//使用二者来比较结果设置总的checkbox
$("#summaryBox").prop("checked",checkedBoxCount == totalBoxCount);
});
// 12.给批量删除的按钮绑定单击响应函数
$("#batchRemoveBtn").click(function(){
// 创建一个数组对象用来存放后面获取到的角色对象
var roleArray = [];
// 遍历当前选中的多选框
$(".itemBox:checked").each(function(){
// 使用this引用当前遍历得到的多选框
var roleId = this.id;
// 通过DOM操作获取角色名称
var roleName = $(this).parent().next().text();
roleArray.push({
"roleId":roleId,
"roleName":roleName
});
});
// 检查roleArray的长度是否为0
if(roleArray.length == 0) {
layer.msg("请至少选择一个执行删除");
return ;
}
// 调用专门的函数打开模态框
showConfirmModal(roleArray);
location.reload();
});
因为实现单选删除和多选删除的操作,可以实现使用一个操作,从前端获取的数据是一个数组,如果是传递一个参数就是单选删除,如果是多个参数的话就是多选删除。
controller:
@ResponseBody
@RequestMapping(value = "/role/remove/by/role/id/array.json")
public ResultEntity<String> removeByRoleIdArray(@RequestBody List<Integer> roleIdList){
roleService.removeRole(roleIdList);
return ResultEntity.successWithoutData();
}
service
/**
* @description: 批量删除
* @author: 斗痘侠
* @date: 2023/11/2 8:16
* @param: roleList
**/
@Override
public void removeRole(List<Integer> roleList) {
RoleExample roleExample = new RoleExample();
RoleExample.Criteria criteria = roleExample.createCriteria();
criteria.andIdIn(roleList);
roleMapper.deleteByExample(roleExample);
}