背景
公司项目需要做到按钮级权限限制,至此有了该文,如有错误,请联系博主指出,多多感谢。
角色配置前后端操作
首先最基本的角色配置,配置该类角色有哪些菜单以及那些菜单的哪些按钮权限
菜单及菜单按钮由前端维护(或者也可以后端数据库维护)
;
前端维护一个JSON文件,直接读取渲染页面即可
JSON文件类似这样,定义菜单及菜单下按钮,声明唯一key和name(角色配置时需要存储对应菜单及按钮的key)
[
{
key: 'Home',
menu: '首页',
},
{
key: 'OrgManagement',
menu: '组织管理',
children: [
{
key: 'RoleConfig',
menu: '角色信息配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'PersonnelConfig',
menu: '人员信息配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'TeamsConfig',
menu: '班组信息配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
],
},
{
key: 'FacilityManagement',
menu: '设施管理',
children: [
{
key: 'LineManagement',
menu: '线体管理',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'DeviceManagement',
menu: '设备管理',
children: [
{
key: 'DeviceManagement_DeviceType',
menu: '设备类型',
disableSelect: true,
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'DeviceManagement_Device',
menu: '设备',
disableSelect: true,
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
],
},
{
key: 'ComponentManagement',
menu: '部件管理',
children: [
{
key: 'ComponentManagement_ComponentType',
menu: '部件类型',
disableSelect: true,
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'ComponentManagement_Component',
menu: '部件',
disableSelect: true,
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
],
},
],
},
{
key: 'OamAlarm',
menu: '运维报警',
children: [
{
key: 'AlarmTemplate',
menu: '报警模版',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'AlarmRecords',
menu: '报警明细',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'ignore',
name: '忽略',
},
{
key: 'createOrder',
name: '生成工单',
},
{
key: 'oneKeyHandle',
name: '一键处理',
},
],
},
{
key: 'AlarmWorkOrderRecords',
menu: '工单明细',
checkPermissions: [],
permissionOptions: [
{
key: 'receive',
name: '接单',
},
],
},
{
key: 'AlarmLevel',
menu: '报警等级配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
],
},
{
key: 'OamPatrol',
menu: '运维巡检',
children: [
{
key: 'PatrolTaskConfig',
menu: '巡检任务配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'FrequencyRulesConfig',
menu: '频率规则配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'DistributeRulesConfig',
menu: '下发规则配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'PatrolTaskManagement',
menu: '巡检任务处理',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
{
key: 'distribute',
name: '下发',
},
{
key: 'handle',
name: '处理',
},
],
},
],
},
{
key: 'Maintenance',
menu: '维修保养',
children: [
{
key: 'MaintenanceDeviceConfig',
menu: '维保设备配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'MaintenancePlanConfig',
menu: '维保计划配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'MaintenanceRemindConfig',
menu: '维保提醒配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'MaintenanceTaskManagement',
menu: '维保任务处理',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
{
key: 'distribute',
name: '下发',
},
],
},
],
},
{
key: 'SpareParts',
menu: '备品备件',
children: [
{
key: 'WarehouseConfig',
menu: '库房库位配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'SpareTypeConfig',
menu: '备件类型配置',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'SpareAccountManagement',
menu: '备件台帐管理',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
{
key: 'PurchaseApply',
menu: '采购申请',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
{
key: 'examine',
name: '审核',
},
{
key: 'createPurchaseOrder',
name: '生成采购单',
},
],
},
{
key: 'ArrivalManagement',
menu: '到货处理',
checkPermissions: [],
permissionOptions: [
{
key: 'confirm',
name: '到货确认',
},
{
key: 'entry',
name: '生成入库',
},
],
},
{
key: 'EntryStorageManagement',
menu: '入库处理',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
{
key: 'entry',
name: '入库',
},
],
},
{
key: 'ExitStorageApply',
menu: '出库申请',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
{
key: 'examine',
name: '审核',
},
],
},
{
key: 'ExitStorageManagement',
menu: '出库处理',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'delete',
name: '删除',
},
{
key: 'exit',
name: '出库',
},
],
},
{
key: 'ExitEntryStorageRecords',
menu: '出入库记录',
},
{
key: 'StocktakingManagement',
menu: '盘库处理',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
{
key: 'stocktaking',
name: '盘库',
},
],
},
{
key: 'TransferManagement',
menu: '调库处理',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
{
key: 'handle',
name: '处理',
},
],
},
],
},
{
key: 'Dict',
menu: '数据字典',
},
{
key: 'Knowledge',
menu: '知识库',
checkPermissions: [],
permissionOptions: [
{
key: 'add',
name: '新增',
},
{
key: 'edit',
name: '编辑',
},
{
key: 'delete',
name: '删除',
},
],
},
]
根据上述JSON文件,渲染出的角色权限编辑页面如下:
该页面可以配置角色菜单及按钮权限,前端请求参数如下:
{
"roleName": "库管员",
"roleSort": 1,
"remark": "库管员",
"menuIds": [
"SpareParts",
"WarehouseConfig",
"SpareTypeConfig",
"SpareAccountManagement",
"PurchaseApply",
"ArrivalManagement",
"EntryStorageManagement",
"ExitStorageApply",
"ExitStorageManagement",
"ExitEntryStorageRecords",
"StocktakingManagement",
"TransferManagement"
],
"permissions": [
"WarehouseConfig/add",
"WarehouseConfig/edit",
"WarehouseConfig/delete",
"SpareTypeConfig/add",
"SpareTypeConfig/edit",
"SpareTypeConfig/delete",
"SpareAccountManagement/add",
"SpareAccountManagement/edit",
"SpareAccountManagement/delete",
"PurchaseApply/add",
"PurchaseApply/edit",
"PurchaseApply/delete",
"PurchaseApply/examine",
"PurchaseApply/createPurchaseOrder",
"ArrivalManagement/confirm",
"ArrivalManagement/entry",
"EntryStorageManagement/add",
"EntryStorageManagement/edit",
"EntryStorageManagement/delete",
"EntryStorageManagement/entry",
"ExitStorageApply/add",
"ExitStorageApply/edit",
"ExitStorageApply/delete",
"ExitStorageApply/examine",
"ExitStorageManagement/add",
"ExitStorageManagement/delete",
"ExitStorageManagement/exit",
"StocktakingManagement/add",
"StocktakingManagement/edit",
"StocktakingManagement/delete",
"StocktakingManagement/stocktaking",
"TransferManagement/add",
"TransferManagement/edit",
"TransferManagement/delete",
"TransferManagement/handle"
]
}
后端需要将该角色有的菜单权限及按钮权限存起来,存在sys_role表中
,表结构如下:
主要关注划红线的两个字段,将上述请求参数,分别用逗号隔开存与这两个字段(菜单以及按钮权限)中,此时角色菜单级及按钮级权限以维护好!(此时可以创建有不同操作权限的角色了)
新增用户时,就可以直接选择对应角色,维护好用户和角色的对应关系,用户就有了对应角色的权限了(用户登录后,查询用户对应菜单权限以及按钮权限,前端可以直接根据返回权限展示菜单及按钮权限【此时连菜单权限也一并做了】)。
其实到这里基本就不会有什么问题了,不同角色用户,只能操作自己有的权限操作。
但是以防万一,后端对应接口加个校验会更加安全。
后端权限校验
注解校验
因为后端用的Java的springBoot框架,可以很方便的进行aop操作。使用注解来实现鉴权操作。
注解如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface RequiresPermissions
{
/**
* 需要校验的权限码
*/
String[] value() default {};
/**
* 验证模式:AND | OR,默认AND
*(这个属性对应场景如下:
* 前端有多个按钮操作可能是调用一个接口/
* 后端新增或者编辑定义的是一个接口)
* 前者属性用 OR,后者用 AND 【权限会存在 and/or 的关系】
*/
Logical logical() default Logical.AND;
}
aop切面类
@Aspect
@Component
public class PreAuthorizeAspect
{
/**
* 构建
*/
public PreAuthorizeAspect()
{}
/**
* 定义AOP签名 (切入所有使用鉴权注解的方法)【切点】
*/
public static final String POINTCUT_SIGN = " @annotation(com.smart.common.security.annotation.RequiresLogin) || "
+ "@annotation(com.smart.common.security.annotation.RequiresPermissions) || "
+ "@annotation(com.smart.common.security.annotation.RequiresRoles)";
/**
* 声明AOP签名
*/
@Pointcut(POINTCUT_SIGN)
public void pointcut()
{}
/**
* 环绕切入
*
* @param joinPoint 切面对象
* @return 底层方法执行后的返回值
* @throws Throwable 底层方法抛出的异常
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable
{
// 注解鉴权
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
checkMethodAnnotation(signature.getMethod());
try
{
// 执行原有逻辑
Object obj = joinPoint.proceed();
return obj;
}
catch (Throwable e)
{
throw e;
}
}
/**
* 对一个Method对象进行注解检查
*/
public void checkMethodAnnotation(Method method) {
// 校验 @RequiresLogin 注解
RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
if (requiresLogin != null) {
AuthUtil.checkLogin();
}
// 校验 @RequiresRoles 注解
RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);
if (requiresRoles != null) {
AuthUtil.checkRole(requiresRoles);
}
// 校验 @RequiresPermissions 注解 【主要看这个校验逻辑】
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
if (requiresPermissions != null) {
AuthUtil.checkPermi(requiresPermissions);
}
}
}
核心处理逻辑在这AuthUtil.checkPermi
public void checkPermi(RequiresPermissions requiresPermissions) {
SecurityContextHolder.setPermission(StringUtils.join(requiresPermissions.value(), ","));
// 判断是否有所有权限或者是单个权限就可以
if (requiresPermissions.logical() == Logical.AND) {
checkPermiAnd(requiresPermissions.value());
}
else {
checkPermiOr(requiresPermissions.value());
}
}
checkPermiAnd
权限与逻辑,所有权限满足才能调用该方法
public void checkPermiAnd(String... permissions) {
Set<String> permissionList = getPermiList();
for (String permission : permissions) {
if (!hasPermi(permissionList, permission)) {
throw new NotPermissionException(permission);
}
}
}
hasPermi逐个判断是否有对应操作权限,只要一个不满足直接抛异常。
checkPermiOr
权限或逻辑,只要有权限满足就能直接调用该方法
public void checkPermiOr(String... permissions) {
// 登录后将用户信息存在ThreadLocal中,直接获取登录用户操作权限列表
Set<String> permissionList = getPermiList();
for (String permission : permissions) {
if (hasPermi(permissionList, permission)) {
return;
}
}
if (permissions.length > 0) {
throw new NotPermissionException(permissions);
}
}
// 所有权限标识
private static final String ALL_PERMISSION = "*:*:*";
// 【or 逻辑】走到这里,只要一个满足条件直接结束,鉴权结束,不会抛异常
public boolean hasPermi(Collection<String> authorities, String permission) {
// 前半段校验是否有所有权限,后半段匹配当前权限是否在当前用户权限中
return authorities.stream().filter(StringUtils::hasText)
.anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission));
}
接口使用的话就比较简单了,直接加个注解添加最开始前端传给后端保存的操作标识即可,如下:
这下前后端双保险,按钮级操作权限到此已经实现。撒花✿✿ヽ(°▽°)ノ✿