若依权限控制前端+后端实现思路梳理(PreAuthorize、hasPermi、v-hasPermi)

一、权限控制引发的思考

引言

最近接手了公司的一个项目,实施反馈说,客户那边要求对不同的权限的用户操作权限做限制。场景就是,比如一个项目列表,这部分数据有可能是针对某个公司某个部门的,对应不同的部门用户能看到的东西是被约束的,同时这个部门有些人员只能查看,而有些人员可以做一些修改,这部分权限控制应当如何实现呢?我当然是懵懵的,毕竟在我看来这样普适性很差,我需要做出很多个性的配置才行,甚至单单是一个前端的按钮显示我都要在每增加一个角色就做一些修改。所以我去了解了一下系统的权限体系,简单来说就是通过给不同用户不同角色,不同角色不同菜单权限来实现的。而这个用户的权限字符串,是在登录的时候进行传回的,至于这个权限字符串,是否安全存储、是否不可修改,这部分内容并不在今天文章的讨论中,这里就不多做介绍,这里我们就假设安全不可篡改。

实现思路

基本的实现思路,我们需要保证当前系统有着完善的菜单配置能力,加上一个权限字符的字段,如图,这里的系统展示图片展示均来自若依,感谢若依开源系统提供的思路。

有了这个字段,我们在配角色的时候,就可以通过查角色菜单,返回对应的权限字符了。而这个权限字符就是我们整个实现的重中之重。然后我们这个时候去配置角色 

通过给角色不同的菜单,给不同用户不同角色,这样就可以实现权限控制了。

这边梳理一下整个流程,如图

 二、具体实现

1、前端 

首先我们先梳理一下前端,后端整体和前端方法相同。

我们要知道前端是如何做到对不同的按钮、菜单做权限判断的,我们就需要先了解一下,vue的一个特性——自定义指令。

我们先来看看什么是指令,在vue中比较常见的指令:v-if、v-model、v-show,如此我们对指令就有一个比较清晰的判断了。那么我们这边为啥要用自定义指令呢,而不用v-if来进行判断呢?为什么呢,主要就是我们希望可以全局使用,而不用在每个vue文件进行编写,那我们就需要全局注册,这个时候我们可以使用v-if和挂载js文件方法来实现,还可以通过自定义指令v-xxx来实现。

这边选择了自定义指令,为什么呢,因为这更加方便且高效,可以在组件全生命周期下进行控制,且整体实现更加的优雅,且也符合vue自定义指令的初衷。

那么如何实现自定义指令呢?

先看看使用方式吧

 <el-button @click="handleAdd" v-hasPermi="['system:menu:add']">新增</el-button>

就是像使用v-if一样使用就行,传入的是一个数组类型的数据,很好理解,可查看权限是多样的,只单一配置显然是不够。

然后就开始写一下通用验证方法吧,代码如下

 /*** 操作权限处理* Copyright (c) 2019 ruoyi*/import store from '@/store'export default {inserted(el, binding, vnode) {const { value } = bindingconst all_permission = "*:*:*";const permissions = store.getters && store.getters.permissionsif (value && value instanceof Array && value.length > 0) {const permissionFlag = valueconst hasPermissions = permissions.some(permission => {return all_permission === permission || permissionFlag.includes(permission)})if (!hasPermissions) {el.parentNode && el.parentNode.removeChild(el)}} else {throw new Error(`请设置操作权限标签值`)}}
}

 接下来我来解释一下这整个代码,首先这个用户权限数据的来源,可以自定,这边不多赘述,接下来我们来重点看一下这个inserted,这个是什么?这其实是组件的一个生命周期,在插入页面时做判断。

接下来那三个参数分别是:

  • el:指令所绑定的元素,可以用来直接操作 DOM,进而实现组件的隐藏。

  • binding:我们通过自定义指令传递的各种参数,大多存在于这个对象中。

  • vnode:Vue 编译生成的虚拟节点。

 而我们这边其实只要使用binding的传的value,也就是我们传入的字符串数组,通过获取用户权限后与配置权限对比,通过就显示,不通过不显示。具体实现细节可以看代码,Arrays.some这个方法的逻辑就是遍历,只要有一个符合,就立刻中止返回true。如此我们的方法已经完成,接下来就是要变成指令。

import hasPermi from './permission/hasPermi'const install = function(Vue) {Vue.directive('hasPermi', hasPermi)
}if (window.Vue) {window['hasPermi'] = hasPermi
}export default install

 如此进行注册,而后在main.js中全局注册就可以正常使用了。

如此我们前端就算是完成了,整体思路就是配置一个自定义指令,通过判断用户权限串,是否有和配置权限相同的权限字符,有则显示。

2、后端

接下来就是后端,整体实现也是获取用户的权限字符串,然后对比,不过使用的方式不同(肯定不一样啊)

这边我们需要了解一个注解 @PreAuthorize,这个注解是SpringSecurity下的一个注解,这个注解的主要作用就是,在方法执行前进行权限验证,支持Spring EL表达式,它是基于方法注解的权限解决方案。

那么这个注解就是一个天然的切点,如此我们就可以通过使用这个注解,从而对接口进行鉴权,实现方法级别的权限控制。

/*** 获取部门列表*/
@PreAuthorize("@ss.hasPermi('system:dept:list')")
@GetMapping("/list")
public AjaxResult list(SysDept dept)
{List<SysDept> depts = deptService.selectDeptList(dept);return success(depts);
}

具体使用就如上述,至于这个@ss是一个具名的Service,通过.调用这个Service的具体方法,这个方法返回类型为Boolean类型。

我们来看看这个Service

package com.ruoyi.framework.web.service;import java.util.Set;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.context.PermissionContextHolder;/*** RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母* * @author ruoyi*/
@Service("ss")
public class PermissionService
{/*** 验证用户是否具备某权限* * @param permission 权限字符串* @return 用户是否具备某权限*/public boolean hasPermi(String permission){if (StringUtils.isEmpty(permission)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())){return false;}PermissionContextHolder.setContext(permission);return hasPermissions(loginUser.getPermissions(), permission);}/*** 验证用户是否不具备某权限,与 hasPermi逻辑相反** @param permission 权限字符串* @return 用户是否不具备某权限*/public boolean lacksPermi(String permission){return hasPermi(permission) != true;}/*** 验证用户是否具有以下任意一个权限** @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表* @return 用户是否具有以下任意一个权限*/public boolean hasAnyPermi(String permissions){if (StringUtils.isEmpty(permissions)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())){return false;}PermissionContextHolder.setContext(permissions);Set<String> authorities = loginUser.getPermissions();for (String permission : permissions.split(Constants.PERMISSION_DELIMETER)){if (permission != null && hasPermissions(authorities, permission)){return true;}}return false;}/*** 判断用户是否拥有某个角色* * @param role 角色字符串* @return 用户是否具备某角色*/public boolean hasRole(String role){if (StringUtils.isEmpty(role)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())){return false;}for (SysRole sysRole : loginUser.getUser().getRoles()){String roleKey = sysRole.getRoleKey();if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))){return true;}}return false;}/*** 验证用户是否不具备某角色,与 isRole逻辑相反。** @param role 角色名称* @return 用户是否不具备某角色*/public boolean lacksRole(String role){return hasRole(role) != true;}/*** 验证用户是否具有以下任意一个角色** @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表* @return 用户是否具有以下任意一个角色*/public boolean hasAnyRoles(String roles){if (StringUtils.isEmpty(roles)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())){return false;}for (String role : roles.split(Constants.ROLE_DELIMETER)){if (hasRole(role)){return true;}}return false;}/*** 判断是否包含权限* * @param permissions 权限列表* @param permission 权限字符串* @return 用户是否具备某权限*/private boolean hasPermissions(Set<String> permissions, String permission){return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));}
}

具体实现逻辑就如上述代码所示,具体验证细节,这肯定是可以自定的,我们可以通过修改代码验证细节就可以实现简单的权限验证。

不过这个注解的我们必须要在SecurityConfig类下开启才可以正常使用。

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

结语

如此我们便可实现这些基本的接口级别的鉴权。整个实现下来,我们发现这必须要求整个系统在构建的伊始就必须要完善的配置结构,否则这个方案是很难流畅的使用的,所以我们在整个项目构建时就应该确定权限配置的基本结构。

然后整个方案其实就是简单的字符串匹配,直接for循环都可以,没什么高大上的,但是整个思路还是非常新奇的,而且也可以开拓视野,毕竟自定义指令、@PreAuthorize注解,这些都可以进行一个实战,整体学习下来收获还是很多的。

如果文章内容有错误欢迎大家留言指正。

整个思路来源若依,再次感谢若依管理系统对我学习的帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/410331.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Kotlin设计模式】Kotlin实现装饰器模式

前言 装饰器模式&#xff08;Decorator Pattern&#xff09;&#xff0c;用于动态地为对象添加新功能&#xff0c;而无需修改其结构&#xff0c;通过使用不用装饰类及这些装饰类的排列组合&#xff0c;可以实现不同的功能和效果&#xff0c;但是这样的效果就是会增加很多类&…

Cypress第二次安装遇到的问题

问题一&#xff1a;吐血&#xff0c;谁会想到node.js的官网访问不了呢&#xff01; 中文网站&#xff1a;http://url.nodejs.cn/download/ 官网&#xff1a;https://nodejs.org/zh-cn nodejs安装的两种方法(官网、NVM安装-node版本切换)不知道这种方式是否可行&#xff0c;还…

62. 不同路径 -dp6

. - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/unique-paths/ 输入&#xff1a;m 3, n 2 输出&#xff1a;3 解释&a…

汽车功能安全--TC3xx LBIST触发时机讨论

目录 1. LBIST架构 2. LBIST寄存器配置 3. LBIST触发时机 LBIST&#xff0c;全称Logic Built-in Self Test。 在TC3xx中&#xff0c;LBIST是一种硬件功能安全机制&#xff0c;目的是为了探测MCU内部逻辑电路的潜伏故障(latent faults)。 从使用者角度来看&#xff0c;只需…

基于x86 平台opencv的图像采集和seetaface6的图像质量评估功能

目录 一、概述二、环境要求2.1 硬件环境2.2 软件环境三、开发流程3.1 编写测试3.2 配置资源文件3.3 验证功能一、概述 本文档是针对x86 平台opencv的图像采集和seetaface6的图像质量评估功能,opencv通过摄像头采集视频图像,将采集的视频图像送给seetaface6的图像质量评估模块…

63. 不同路径 II -dp7

63. 不同路径 IIhttps://leetcode.cn/problems/unique-paths-ii/ 输入&#xff1a;obstacleGrid [[0,0,0],[0,1,0],[0,0,0]] 输出&#xff1a;2 解释&#xff1a;3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有 2 条不同的路径&#xff1a; 1. 向右 -> 向右 ->…

对各项数据的统计汇总,集中展示,便于查看厂区情况的智慧物流开源了。

智慧物流视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。构建基于Ai技术的…

关于kafka的分区和消费者之间的关系

消费者和消费者组 当生产者向 Topic 写入消息的速度超过了消费者&#xff08;consumer&#xff09;的处理速度&#xff0c;导致大量的消息在 Kafka 中淤积&#xff0c;此时需要对消费者进行横向伸缩&#xff0c;用多个消费者从同一个主题读取消息&#xff0c;对消息进行分流。 …

yolov8 安装流程

1、克隆远端代码 git clone https://gitcode.com/gh_mirrors/ul/ultralytics.git 2、配置pyshon环境安装 3.10的版本&#xff0c;注意3.12后期会出现标注都在顶部的问题 使用idea可以在项目结构中直接添加python SDK 3、下载依赖&#xff0c;安装8.0210&#xff0c;注意新依赖…

脑靶向肽 ;SHp ;CLEVSRKNC ;缺血归巢肽

【脑靶向肽 SHp 简介】 SHp多肽是一种抗肿瘤多肽&#xff0c;它可以通过激活P53基因&#xff0c;调节细胞凋亡相关基因的蛋白表达&#xff0c;从而抑制肿瘤细胞的增殖并诱导细胞凋亡。在最新的研究中&#xff0c;SHp多肽被发现可以促进T细胞对肿瘤细胞的杀伤作用&#xff0c;显…

Vue3源码调试-第三篇

前言 上两篇已经调试完packages/runtime-dom/src/index.ts下的createApp函数的第一行了&#xff0c;接下来我们看下一行 injectNativeTagCheck 首先说下这个__DEV__估计也是定义在dev.js下&#xff0c;又或者是哪里的&#xff0c;这里控制台输出是true&#xff0c;那我估计是…

深入解析css-学习小结

绪论 盒模型 层叠 优先级 继承 层叠 层叠指规则冲突时&#xff0c;如何选择规则。规则冲突解决顺序&#xff1a; 样式表来源 用户代理样式 用户代理样式&#xff1a;浏览器默认样式 作者样式表&#xff1a;你自己写的css样式 作者样式表会覆盖用户代理样式&#xff0c;因…

Java 入门指南:Java IO流 —— 字节流

何为Java流 Java 中的流&#xff08;Stream&#xff09; 是用于在程序中读取或写入数据的抽象概念。流可以从不同的数据源&#xff08;输入流&#xff09;读取数据&#xff0c;也可以将数据写入到不同的目标&#xff08;输出流&#xff09;。流提供了一种统一的方式来处理不同…

2024.8.27 作业

1> 提示并输入一个字符串&#xff0c;统计该字符串中字母个数、数字个数、空格个数、其他字符的个数 #include <iostream>using namespace std;int main() {string s;cout << "请输入字符串>>>";getline(cin,s);int letter0,digit0,blank0,…

华为eNSP:静态路由配置、浮动路由配置

静态路由&#xff1a; 一、拓扑图 二、路由器配置 2.1&#xff1a;配置接口 R1&#xff1a; [r1]int g0/0/0 [r1-GigabitEthernet0/0/0]ip add 192.168.1.254 24 [r1-GigabitEthernet0/0/0]qu [r1]int g0/0/1 [r1-GigabitEthernet0/0/1]ip add 10.1.1.1 24 [r1-GigabitEth…

CMake之PUBLIC、PRIVATE、INTERFACE

竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生~ 个人主页&#xff1a; rainInSunny | 个人专栏&#xff1a; C那些事儿、 Qt那些事儿 文章目录 写在前面抽象版解释头文件和链接库传递测试代码结构PUBLIC传递PRIVATE传递INTERFACE传递 写在前面 使用CMake必然离不开target_include_dir…

第2章 双向链表

双向链表 概念 对链表而言&#xff0c;双向均可遍历是最方便的&#xff0c;另外首尾相连循环遍历也可大大增加链表操作的便捷性。因 此&#xff0c;双向循环链表&#xff0c;是在实际运用中是最常见的链表形态。 基本操作 与普通的链表完全一致&#xff0c;双向循环链表虽然…

FPGA 如何进入 AI 领域的思考

FPGA在AI领域如何发力&#xff0c;如何抢碗饭吃&#xff1f;大多数提到是硬件加速&#xff0c;在AI工程里&#xff0c;完成数据前处理&#xff08;加速&#xff09;。大家很少提到AI模型的本身的推理过程&#xff0c;让FPGA成为AI模型的推理/算力芯片&#xff0c;这自然是 FPGA…

2535. 解密 [CSP-J 2022]

代码 #include <bits/stdc.h> using namespace std; long long m,n; int check(int x){if(x * (m - x) n) return 0;if(x * (m - x) < n) return 1;if(x * (m - x) > n) return 2; } int main(){int k;cin >> k;while(k--){long long e, d,p0,q0;scanf(&q…

如何抠去PPT图片的背景?推荐这款AI智能抠图软件!

做ppt的过程中&#xff0c;我们会用到各式各样的图片素材&#xff0c;其中有些图片不能完全满足我们的需求&#xff0c;得先对图片进行处理&#xff0c;最常见的是抠图&#xff0c;去除图片原有的背景&#xff0c;得到一张包含透明像素的图片&#xff0c;方便我们排版PPT页面上…