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

news2024/11/14 1:32:56

一、权限控制引发的思考

引言

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

实现思路

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

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

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

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

 二、具体实现

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 } = binding
    const all_permission = "*:*:*";
    const permissions = store.getters && store.getters.permissions

    if (value && value instanceof Array && value.length > 0) {
      const permissionFlag = value

      const 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.coloradmin.cn/o/2068550.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云岚到家 第一天

你的项目是做什么业务的&#xff1f; 家政服务&#xff0c;我的项目是一个家政o2o平台。o2o&#xff08;Online To Offline&#xff09;是将线下商务的机会与互联网的技术结合 什么商业模式&#xff1a; 项目涉及哪些角色&#xff1f; 本项目涉及如下角色&#xff1a; 家政…

技术前沿:WebRTC与H.265编码的兼容性挑战与应对策略

WebRTC&#xff08;Web Real-Time Communication&#xff09;是一种支持网页浏览器进行实时语音通话、视频聊天以及P2P文件共享的技术。然而&#xff0c;标准的WebRTC API在大多数浏览器中默认并不支持H.265&#xff08;也称为HEVC&#xff0c;高效视频编码&#xff09;编码。这…

Android 中native C++创建thread的几种方式

Android native 开发&#xff0c;工作中最常用的两种创建thread的方法&#xff0c;总结如下&#xff1a; 使用 pthread 库 &#xff08;1&#xff09;概念&#xff1a;它是一套在类 Unix 操作系统上进行多线程编程的接口&#xff0c; 而android 基于Linux , Linux就是一个类 U…

用序列模型(GPT Bert Transformer等)进行图像处理的调研记录

Visual Autoregressive Modeling: Scalable Image Generation via Next-Scale Prediction 北大和字节团队的一篇VLM&#xff0c;在生成任务上&#xff0c;用GPT范式&#xff0c;声称在FID上超过了DIT&#xff0c;SD3和SORA。开源。首先是multi-scale的VQVAE&#xff0c;然后是…

Datawhale X 李宏毅苹果书 AI夏令营 学习笔记(一)

局部极小值与鞍点 在优化过程中&#xff0c;模型可能会遇到局部极小值(local minima)或鞍点(saddle point)&#xff0c;这些位置梯度为零&#xff0c;使得模型停止进步或训练缓慢。 局部极小值是损失函数的局部最低点&#xff0c;而鞍点则是梯度为零但不是局部极小/极大值的点…

Leetcode JAVA刷刷站(80)删除有序数组中的重复项 ||

一、题目概述 二、思路方向 为了解决这个问题&#xff0c;我们可以使用双指针技术。一个指针&#xff08;我们称之为i&#xff09;用于遍历数组&#xff0c;另一个指针&#xff08;我们称之为j&#xff09;用于记录不重复或只重复一次的元素应该放置的位置。同时&#xff0c;我…

创新实践:流媒体服务器如何推动WebRTC支持H.265及JS硬软解码(MSE硬解、WASM软解)

为了实现这一全面的解决方案&#xff0c;我们投入了近半年的时间进行调研与研发。我们的主要目标是&#xff1a;让流媒体服务器能够直接传输H.265编码的视频&#xff0c;而无需将其转码为H.264&#xff0c;从而使Chrome浏览器能够无缝解码并播放H.265视频。 值得注意的是&#…

TinaSDKV2.0 自定义系统开发

TinaSDKV2.0 自定义系统开发 什么是自定义系统&#xff1f; TinaSDK Kconfig界面配置 Tina Linux采用 Kconfig 机制对 SDK 和内核进行配置。 Kconfig 是一种固定格式的配置文件。Linux 编译环境中的 menuconfig 程序可以识别这种格式的配置文件&#xff0c;并提取出有效信息…

Flink常见数据源(source)使用教程(DataStream API)

前言 一个 Flink 程序,其实就是对 DataStream 的各种转换。具体来说,代码基本上都由以下几部分构成,如下图所示: 获取执行环境(execution environment)读取数据源(source)定义基于数据的转换操作(transformations)定义计算结果的输出位置(sink)触发程序执行(exec…

探索上门回收旧衣物系统源码开发的创新与挑战

在当今社会&#xff0c;随着环保意识的日益增强和可持续发展的全球趋势&#xff0c;旧衣物回收与再利用成为了一个备受关注的议题。为了响应这一需求&#xff0c;开发一套高效、便捷的上门回收旧衣物系统&#xff0c;不仅有助于减少环境污染&#xff0c;还能促进资源的循环利用…

[数据集][目标检测]电力场景输电线导线散股检测数据集VOC+YOLO格式3890张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3890 标注数量(xml文件个数)&#xff1a;3890 标注数量(txt文件个数)&#xff1a;3890 标注…

Koa商城项目-商城模块(前端)

项目地址 koa_system: &#x1f525;&#x1f525;&#x1f525;Koa2 React商城项目前端-React Antd前端-Vue2 Element-plus后端-Koa2 Sequelizehttps://gitee.com/ah-ah-bao/koa_system 欢迎大家点击查看,方便的话点一个star~ 项目结构 Vue2Admin和Vue3Admin版本的后台还…

Deep-Live-Cam启动

实验环境 实验时间&#xff1a;2024年8月windows 10 专业版能跟老外对喷的网络环境基于 Anaconda 创建 Python 3.10.x 环境 其它依赖 下载生成工具&#xff1a;https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/安装单个组件&#xff1a;共三个。 安装ffm…

Python酷库之旅-第三方库Pandas(093)

目录 一、用法精讲 396、pandas.Series.to_frame方法 396-1、语法 396-2、参数 396-3、功能 396-4、返回值 396-5、说明 396-6、用法 396-6-1、数据准备 396-6-2、代码示例 396-6-3、结果输出 397、pandas.Series.to_xarray方法 397-1、语法 397-2、参数 397-3、…

算法的学习笔记—二叉树中和为某一值的路径

&#x1f600;前言 在二叉树中寻找和为某一特定值的路径问题是一个经典的面试题&#xff0c;考察了对二叉树的遍历能力以及递归和回溯算法的理解和应用。本文将详细解析这一问题&#xff0c;并提供一个Java实现。 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 &#x1…

Java工具类之字符串类(超详细)

1、 字符串类 字符串是我们在编程中最常使用的一种数据类型&#xff0c;Java中用类来描述字符串&#xff0c;其中最常用的字符串处理类是String&#xff0c;此外还有StringBuffer和StringBuilder。在本节&#xff0c;我们会了解每种字符串处理类的特点&#xff0c;以便能在应用…

C语言基础(十二)

指针的用法&#xff1a; 测试代码1&#xff1a; #include "date.h" #include <stdio.h> // 函数声明&#xff0c;用于交换两个整数的值&#xff0c;通过指针传递 void swap(int *a, int *b); int main() { int time getTime();int nums[3] {3, 1, 4};…

CentOS 7.9 安装部署 EchoMimic

EchoMimic学习地址&#xff1a;https://github.com/BadToBest/EchoMimic CentOS 7.9 安装部署 EchoMimic 1、创建虚拟机2、基础环境准备2.1 安装驱动2.2 下载 Anaconda3-2024.06-1-Linux-x86_64.sh2.3 下载完成后执行2.4 退出客户端重新连一下2.5 查看python版本及cuda版本 3 E…

冷硬缓存——利用缓存滥用绕过 RPC 接口安全

介绍 MS-RPC 是 Windows 操作系统的基石之一。早在 20 世纪 90 年代发布,它就已扎根于系统的大部分部分。服务管理器?RPC。Lsass?RPC。COM?RPC。甚至一些针对域控制器的域操作也使用 RPC。鉴于 MS-RPC 已经变得如此普遍,您可以预料到它已经受到严格的审查、记录和研究。 …

TinyVision 使用 SyterKit 启动 Linux 6.7 主线内核

TinyVision 使用 SyterKit 启动 Linux 6.7 主线内核 SyterKit SyterKit 是一个纯裸机框架&#xff0c;用于 TinyVision 或者其他 v851se/v851s/v851s3/v853 等芯片的开发板&#xff0c;SyterKit 使用 CMake 作为构建系统构建&#xff0c;支持多种应用与多种外设驱动。同时 Sy…