背景
公司项目需要做到按钮级权限限制,至此有了该文,如有错误,请联系博主指出,多多感谢。
角色配置前后端操作
首先最基本的角色配置,配置该类角色有哪些菜单以及那些菜单的哪些按钮权限
菜单及菜单按钮由前端维护(或者也可以后端数据库维护)
;
前端维护一个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));}
接口使用的话就比较简单了,直接加个注解添加最开始前端传给后端保存的操作标识即可,如下:
这下前后端双保险,按钮级操作权限到此已经实现。撒花✿✿ヽ(°▽°)ノ✿