基于Vue实现后台系统权限控制的示例代码

用Vue/React这类双向绑定框架做后台系统再适合不过,后台系统相比普通前端项目除了数据交互更频繁以外,还有一个特别的需求就是对用户的权限控制,那么如何在一个Vue应用中实现权限控制呢?下面是我的一点经验。

权限控制是什么

在权限的世界里服务端提供的一切都是资源,资源可以由请求方法+请求地址来描述,权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源。具体的说,前端对资源的访问通常是由界面上的按钮发起,比如删除某条数据;或由用户进入某一个页面发起,比如获取某个列表数据。这两种形式覆盖了资源请求的大部分场景,因此权限控制也可以被笼统的分成菜单权限控制和按钮权限控制。

Vue菜单权限控制

菜单是对路由的直接体现,菜单控制实际上就是路由控制。实现路由控制一个简单的方式是,在路由的before钩子里校验当前即将跳转的路由地址是否有权访问,根据校验结果决定路由是否放行,伪码:

router.beforeEach((to, from, next) => {
  //权限校验
  let pass = valid(to);
  if(!pass){
    return console.log('无权访问');
  }
  next();
});

这种实现方式既简单又直观,用于简单的系统非常合适,但这么做本质上是将所有路由全部注册了,直接带来的缺点有两个:一、如果路由组件不是按需加载的话,应用将加载大量冗余代码;二、每次跳转都要遍历一次完整路由是对计算能力的浪费,对于路由总数较大的应用很不可取。

理想的实现方式是本地保存完整路由,但并不立即初始化Vue应用,待用户登录拿到权限后,用菜单权限筛选出可用路由,再用可用路由初始化Vue应用。也就是说,要将登录页独立出去做成一个单独的页面,登录后将用户数据保存在本地,再通过url跳转到Vue应用所在页面,Vue应用启动前通过本地用户数据完成路由筛选,然后初始化Vue应用,伪码如下:

//main.js
let user = sessionStorage.getItem('user');
if (user) {
  user = JSON.parse(user);
  //筛选得到实际路由
  let fullPath = require('fullPath.js');
  let routes = filter(fullPath, user.menus);
  //创建路由对象
  let router = new Router({routes});
  //生成Vue实例
  new Vue({
    el: '#app',
    router,
    render: h => h(App)
  });
}else{
  location.href = '/login/';
}

这样我们就根据用户权限生成了一套”定制”路由,这时我们还希望能直接用路由生成导航菜单,常规的路由数据可能无法满足菜单组件的需求,所以我们可以事先在路由的meta数据里维护上菜单数据,比如菜单名称菜单图标等,只要在模板中通过$router.options就可以访问到当前路由数据,如果使用element-ui的菜单组件实现,代码大致是这样的:

<el-menu router>
  <el-menu-item v-for="(route, index) in $router.options.routes[2].children"
  :route="route"
  :index="route.name">
    <i class="ion" v-html="route.icon"></i>{{route.name}}
  </el-menu-item>
</el-menu>

当然这样只能循环出一级菜单,如果还有二级路由需要对应二级菜单的话,就得判断并循环children节点,比较简单就不放更多代码了,菜单权限控制到这里就完成了。

Vue按钮权限控制

按钮权限控制与菜单权限控制的实现思路类似,也是根据用户权限判断各个按钮的显示与否,方式无非是v-if或自定义指令,而且只要将v-if背后的权限校验逻辑抽象成方法,无论是代码量还是使用形式上都跟自定义指令几乎一样,但v-if的特点是它会响应数据变化,因此随着应用的运行会频繁触发权限校验,而权限在应用的整个生命周期内其实只需校验一次,为了避免无谓的程序执行,这里可以用自定义指令来实现,伪码:

Vue.directive('has', {
 bind: function (el, binding) {
  if(!has(binding.value)){
    el.parentNode.removeChild(el);
  }
 }
});

//用法:
<btn v-has='get,/sources'>按钮</btn>

注意在指令bind回调里有一个has()方法,这就是权限校验方法,我们同时将这个方法全局混合到Vue对象中,使应用里的每个组件都可以访问到这个方法,便于为界面上的v-if提供支持,例如:

<div v-if="has('get,/sources') && something">
  一个需要同时具备'get,/sources'权限和somthing为真值才显示的div
</div>

这样一来凡是需要依据权限实现的按钮显隐控制和界面变化都可以很方便的实现。

但按钮权限控制真正麻烦的地方不在于如何实现,而在于高昂的维护成本。我们假设按钮Btn绑定了点击回调Fn,回调Fn里发起了请求Req,请求Req需要某个资源的访问权限,最终你要根据用户是否拥有Req的权限,决定Btn是否显示,而Req跟Btn之间并没有直接关联,所以我们就要人肉维护他们的关系,一个复杂项目里的按钮有个几十上百都很正常,随着业务的变更去维护这么多按钮的权限,想想都头疼。

有一个方法可以绕开这个烂摊子,那就是前端放弃对视图层的控制,退到请求层面,在请求发起前集中拦截,这时可以直接根据请求方法和请求地址来校验权限,除了实现一个拦截器之外不需要额外的代码,可以说非常优雅了。以axios为例,拦截器大概长这样:

axios.interceptors.request.use(function (config) {
 if(!has(config)){
 //验证不通过
  return Promise.reject({
   message: `no permission`
  });
 }
 return config;
});

但如果仅仅这样做权限控制,界面上将显示出所有的按钮,用户看到的按钮却不一定可以点击,这种体验我认为只能停留在理论层面,根本无法应用到实际产品中。请求控制可以作为整个控制体系的第二道防线,或某些特殊情况下的辅助手段,最终还是要回到按钮控制的思路上来。

那么怎样能尽可能方便的采集到每个按钮所需的权限呢?按钮和权限之间隔着两层东西,第一层是click回调,第二层是回调里的AJAX请求,不想人肉维护就得想办法突破这两层隔阂,让按钮和权限产生联系,按钮必然要绑定click事件,最理想的采集方式是在绑定事件的同时得到所需权限,让一切自然而然的发生,比如这样,

<btn v-do="Fn">按钮</btn>

如果Fn能以某种形式采集到内部的AJAX请求参数,并转化成权限信息传递出来就完美了,然而我没找到可行的方法,并且这种形式在应用上也存在缺陷,因为不一定每个操作按钮都会发起AJAX请求,比如编辑按钮本身并不会触发请求,真正触发请求的是另一个保存按钮,所以这个思路只是看起来很美。

那么退而求其次的做法是让按钮和请求联系起来,比如说按钮涉及一个名称为A的请求,那么我希望权限指令可以这样写,

<btn v-has="A" @click="Fn">按钮</btn>

比完美形态是差了不少,但起码不需要手动维护到'get,/resources'这个级别了,这里对A的实现可以有多种形式,比如A可以是一个包含两个属性的对象:

const A = {
 p: ['put,/menu/**'],
 r: params => {
  return axios.put(`/menu/${params.id}`, params)
 }
};

//用作权限:
<btn v-has="[A]" @click="Fn">按钮</btn>
//用作请求:
function Fn(){
  A.r().then((res) => {})
}

通常我们会将项目里所有的api放在一个api模块里集中管理,在写api时顺便就把权限给维护了,换来的是在组件界面里可以直接用请求名称来描述权限,而不需要来回奔波于界面和api模块之间,一定程度上实现了关注点分离,而且has指令还可以进一步做优化,例如参数只需要接收A,指令内部根据约定自动访问A.p来获取权限,还可以接收数组,允许多个权限联合校验。

后记

好了,这就是我对前端权限控制的一些实践和思考,如有不当欢迎指正。

最后吐槽一下Element-UI,真心难看。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • vue-router 权限控制的示例代码

    最近搭建了公司的后台管理系统, 而且系统还比较庞大, 要实现以下几点: 菜单权限, 根据不同权限显示不同的菜单 操作权限, 比如有些账号没有新增权限, 有些没有修改或者删除权限 数据权限, 比如统计概况, 普通管理员不能看到公司营业概况,但能看到自己所属区域的概况 显示权限, 比如列表, 运营能看到那一列的签约金额,但市场不能看到签约金额这一列 到目前为止还在构建中, 已经解决菜单权限和操作权限. 菜单权限: 最开始的时候也是按照楼上给出的方法,本地先配置一套路由,然后登陆成功后会从服务器返回一

  • 基于Vue实现后台系统权限控制的示例代码

    用Vue/React这类双向绑定框架做后台系统再适合不过,后台系统相比普通前端项目除了数据交互更频繁以外,还有一个特别的需求就是对用户的权限控制,那么如何在一个Vue应用中实现权限控制呢?下面是我的一点经验. 权限控制是什么 在权限的世界里服务端提供的一切都是资源,资源可以由请求方法+请求地址来描述,权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源.具体的说,前端对资源的访问通常是由界面上的按钮发起,比如删除某条数据:或由用户进入某一个页面发起,比如获取某个列表数据

  • Vue 简单实现前端权限控制的示例

    简要说明 最近写了一下vue控制权限(菜单.路由)的项目,用了vuex.addRoutes动态添加路由方法等,总共100多行代码,跟大家分享一下~ 逻辑梳理 除登录接口.退出接口外,其余接口增加token验证. 打开页面时请求获取菜单接口,请求不成功说明未登录,给route默认添加login页面以及 * 重定向. 登录成功后获取到token,把token存入session以及请求头. 登录成功后获取菜单接口,请求回来的路由和vuex里面全部的路由进行匹配,获取component. 把获取完com

  • vue实现菜单权限控制的示例代码

    大家在做后台管理系统时一般都会涉及到菜单的权限控制问题.当然解决问题的方法无非两种--前端控制和后端控制.我们公司这边的产品迭代速度较快,所以我们是从前端控制路由迭代到后端控制路由.下面我会分别介绍这两种方法的优缺点以及如何实现(不熟悉vue-router API的同学可以先去官网看一波API哈). 我先简单说下项目的需求:如下图所示,有一级菜单和二级菜单,然后不同的人登录进去会展示不同的菜单. 前端控制路由的思路:将所有的路由映射表都拿到前端来维护,就是我的router.js里面将所有的菜单p

  • Springboot+Vue+shiro实现前后端分离、权限控制的示例代码

    本文总结自实习中对项目的重构.原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelAndView,逐渐有了前后端分离的想法,由于之前,没有接触过,主要参考的还是网上的一些博客教程等,初步完成了前后端分离,在此记录以备查阅. 一.前后端分离思想 前端从后端剥离,形成一个前端工程,前端只利用Json来和后端进行交互,后端不返回页面,只返回Json数据.前后端之间完全通过public A

  • Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码

    目录 前言 本文主要的功能 一.准备工作 1.统一错误码枚举 2.统一json返回体 3.返回体构造工具 4.pom 5.配置文件 二.数据库表设计 初始化表数据语句 三.Spring Security核心配置:WebSecurityConfig 四.用户登录认证逻辑:UserDetailsService 1.创建自定义UserDetailsService 2.准备service和dao层方法 五.用户密码加密 六.屏蔽Spring Security默认重定向登录页面以实现前后端分离功能 1.实

  • Spring Security实现统一登录与权限控制的示例代码

    目录 项目介绍 统一认证中心 配置授权服务器 配置WebSecurity 登录 菜单 鉴权 资源访问的一些配置 有用的文档 项目介绍 最开始是一个单体应用,所有功能模块都写在一个项目里,后来觉得项目越来越大,于是决定把一些功能拆分出去,形成一个一个独立的微服务,于是就有个问题了,登录.退出.权限控制这些东西怎么办呢?总不能每个服务都复制一套吧,最好的方式是将认证与鉴权也单独抽离出来作为公共的服务,业务系统只专心做业务接口开发即可,完全不用理会权限这些与之不相关的东西了.于是,便有了下面的架构图:

  • Vue 实现前端权限控制的示例代码

    登录&&权限流程图 前言 首先我们确定的权限控制分为三大部分,其中根据粒度大小分的更细: 登录权限控制 页面权限控制 菜单中的页面是否可以被访问 页面中的按钮 (增.删.改.查)的权限控制是否显示 接口权限控制 一.登录权限控制 登录访问权限控制是对用户的校验.在用户登录成功之后,后台将返回一个token,之后前端每次进行接口请求的时候,都要带上这个token.后台拿到这个token后进行判断,如果此token确实存在并且没有过期,则可以通过访问.如果token不存在或后台判断已过期,则会

  • vue实现后台管理权限系统及顶栏三级菜单显示功能

    •效果演示地址 项目demo展示 重要功能总结 权限功能的实现 权限路由思路: 根据用户登录的roles信息与路由中配置的roles信息进行比较过滤,生成可以访问的路由表,并通过router.addRoutes(store.getters.addRouters)动态添加可访问权限路由表,从而实现左侧和顶栏菜单的展示. 实现步骤: 1.在router/index.js中,给相应的菜单设置默认的roles信息: 如下:给"权限设置"菜单设置的权限为:meta:{roles: ['admin

  • Angular中使用ui router实现系统权限控制及开发遇到问题

    前端去实现权限控制听起来有点扯淡(实际也有点扯淡),掩耳盗铃,主要是担心安全问题,但是如果在前后端分离的情况下,需要做一个带有权限控制的后台管理系统,angular基于ui-router应该怎么做呢? 权限的设计中比较常见的就是RBAC基于角色的访问控制,基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合.每一种角色对应一组相应的权限. 一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限.这样做的好处是,不必在每次创建用户时都进行分配

随机推荐