前端面试:权限控制

来源:http://www.chinese-glasses.com 作者:Web前端 人气:195 发布时间:2020-03-24
摘要:时间: 2019-11-02阅读: 207标签: 权限0.前言 Vue2.0用户权限控制解决方案,vue2.0权限控制 Vue-Access-Control是一套基于Vue/Vue-Router/axios实现的前端用户权限控制解决方案,通过对路由、视图、请求

时间: 2019-11-02阅读: 207标签: 权限0.前言

Vue2.0用户权限控制解决方案,vue2.0权限控制

Vue-Access-Control是一套基于Vue/Vue-Router/axios 实现的前端用户权限控制解决方案,通过对路由、视图、请求三个层面的控制,使开发者可以实现任意颗粒度的用户权限控制。

安装

版本要求

Vue 2.0x
Vue-router 3.x
获取

git:git clone https://github.com/tower1229/Vue-Access-Control.git

npm:npm i vue-access-control

运行

//开发
npm run dev

//构建
npm build

概述

整体思路

会话开始之初,先初始化一个只有登录路由的Vue实例,在根组件created钩子里将路由定向到登录页,用户登录成功后前端拿到用户token,设置axios实例统一为请求headers添加{"Authorization":token}实现用户鉴权,然后获取当前用户的权限数据,主要包括路由权限和资源权限,之后动态添加路由,生成菜单,实现权限指令和全局权限验证方法,并为axios实例添加请求拦截器,至此完成权限控制初始化。动态加载路由后,路由组件将随之加载并渲染,而后展现前端界面。

为解决浏览器刷新路由重置的问题,拿到token后要将其保存到sessionStorage,根组件的created钩子负责检查本地是否已有token,如果有则无需登录直接用该token获取权限并初始化,如果token有效且当前路由有权访问,将加载路由组件并正确展现;若当前路由无权访问将按路由设置跳转404;如果token失效,后端应返回4xx状态码,前端统一为axios实例添加错误拦截器,遇到4xx状态码执行退出操作,清除sessionStorage数据并跳转到登录页,让用户重新登录。

最小依赖原则

Vue-Access-Control的定位是单一领域解决方案,除了Vue/Vue-Router/axios之外没有其他依赖,理论上可以无障碍的应用到任何有权限控制需求的Vue项目中,项目基于webpack 模板开发构建,大多数新项目可以直接基于检出代码继续开发。需要说明的是,项目额外引入的Element-UI和CryptoJS仅用于开发演示界面,他们不是必须且与权限控制毫无关系,项目应用中可以自行取舍。

目录结构

src/
 |-- api/     //接口文件
 |  |-- index.js    //输出通用axios实例
 |  |-- account.js   //按业务模块组织的接口文件,所有接口都引用./index提供的axios实例
 |-- assets/
 |-- components/
 |-- router/
 |  |-- fullpath.js   //完整路由数据,用于匹配用户的路由权限得到实际路由
 |  `-- index.js   //输出基础路由实例
 |-- views/
 |-- App.vue
 ·-- main.js

 

数据格式约定

路由权限数据必须是如下格式的对象数组,id和parent_id相同的两个路由具有上下级关系,如果希望使用自定义格式的路由数据,需要修改路由控制的相关实现。

[
 {
  "id": "1",
  "name": "菜单1",
  "parent_id": null,
  "route": "route1"
 },
 {
  "id": "2",
  "name": "菜单1-1",
  "parent_id": "1",
  "route": "route2"
 }
 ]

资源权限数据必须是如下格式的对象数组,每个对象代表一个RESTful请求,支持带参数的url。

[
 {
  "id": "2c9180895e172348015e1740805d000d",
  "name": "账号-获取",
  "url": "/accounts",
  "method": "GET"
 },
 {
  "id": "2c9180895e172348015e1740c30f000e",
  "name": "账号-删除",
  "url": "/account/**",
  "method": "DELETE"
 }
]

路由控制

路由控制包括动态注册路由和动态生成菜单两部分。

动态注册路由

最初实例化的路由仅包括登录和404两个路径,我们期待完整的路由是这样的:

[{
 path: '/login',
 name: 'login',
 component: (resolve) => require(['../views/login.vue'], resolve)
}, {
 path: '/404',
 name: '404',
 component: (resolve) => require(['../views/common/404.vue'], resolve)
}, {
 path: '/',
 name: '首页',
 component: (resolve) => require(['../views/index.vue'], resolve),
 children: [{
 path: '/route1',
 name: '栏目1',
 meta: {
  icon: 'icon-channel1'
 },
 component: (resolve) => require(['../views/view1.vue'], resolve)
 }, {
 path: '/route2',
 name: '栏目2',
 meta: {
  icon: 'ico-channel2'
 },
 component: (resolve) => require(['../views/view2.vue'], resolve),
 children: [{
  path: 'child2-1',
  name: '子栏目2-1',
  meta: {

  },
  component: (resolve) => require(['../views/route2-1.vue'], resolve)
 }]
 }]
}, {
 path: '*',
 redirect: '/404'
}]

那么接下来就需要获取首页以及其子路由们,思路是事先在本地存一份整个项目的完整路由数据,然后根据用户权限对完整路由进行筛选。

筛选的实现思路是先将后端返回的路由数据处理成如下哈希结构:

let hashMenus = {
 "/route1":true,
 "/route1/route1-1":true,
 "/route1/route1-2":true,
 "/route2":true,
 ...
}

然后遍历本地完整路由,在循环中将路径拼接成上述结构中的key格式,通过hashMenus[route]就可以判断路由是否匹配,具体实现见App.vue文件中的getRoutes()方法。

如果后端返回的路由权限数据与约定不同,就需要自行实现筛选逻辑,只要能得到实际可用的路由数据就可以,最终使用addRoutes()方法将他们动态添加到路由实例中,注意404页面的模糊匹配一定要放在最后。

动态菜单

路由数据可以直接用来生成导航菜单,但路由数据是在根组件中得到的,导航菜单存在于index.vue组件中,显然我们需要通过某种方式共享菜单数据,方法有很多,一般来说首先想到的是Vuex,但菜单数据在整个用户会话过程中不会发生改变,这并不是Vuex的最佳使用场景,而且为了尽量减少不必要的依赖,这里用了最简单直接的方法,把菜单数据挂在根组件data.menuData上,在首页里用this.$parent.menuData获取。

另外,导航菜单很可能会有添加栏目图标的需求,这可以通过在路由中添加meta数据实现,例如将图标class或unicode存到路由meta里,模板中就可以访问到meta数据,用来生成图标标签。

在多角色系统中可能遇到的一个问题是,不同角色有一个名字相同但功能不同的路由,比如说系统管理员和企业管理员都有”账号管理”这个路由,但他们的操作权限和目标不同,实际上是两个完全不同的界面,而Vue不允许多个路由同名,因此路由的name必须做区分,但把区分后的name显示在前端菜单上会很不美观,为了让不同角色可以享有同一个菜单名称,我们只要将这两个路由的meta.name都设置成”账号管理”,在模板循环时优先使用meta.name就可以了。

菜单的具体实现可以参考views/index.vue。

视图控制

视图控制的目标是根据当前用户权限决定界面元素显示与否,典型场景是对各种操作按钮的显示控制。实现视图控制的本质是实现一个权限验证方法,输入请求权限,输出是否获准。然后配合v-if或jsx或自定义指令就能灵活实现各种视图控制。

全局验证方法

验证方法的的实现本身很简单,无非是根据后端给出的资源权限做判断,重点在于优化方法的输入输出,提升易用性,经过实践总结最终使用的方案是,将权限跟请求同时维护,验证方法接收请求对象数组为参数,返回是否具有权限的布尔值。

请求对象格式:

//获取账户列表
const request = {
 p: ['get,/accounts'],
 r: params => {
 return instance.get(`/accounts`, {params})
 }
}

权限验证方法$_has()的调用格式:

v-if="$_has([request])"

权限验证方法的具体实现见App.vue中Vue.prototype.$_has方法。

将权限验证方法全局混入,就可以在项目中很容易的配合v-if实现元素显示控制,这种方式的优点在于灵活,除了可以校验权限外,还可以在判断表达式中加入运行时状态做更多样性的判断,而且可以充分利用v-if响应数据变化的特点,实现动态视图控制。

具体实现细节参考基于Vue实现后台系统权限控制中的相关章节。

自定义指令

v-if的响应特性是把双刃剑,因为判断表达式在运行过程中会频繁触发,但实际上在一个用户会话周期内其权限并不会发生变化,因此如果只需要校验权限的话,用v-if会产生大量不必要的运算,这种情况只需在视图载入时校验一次即可,可以通过自定义指令实现:

//权限指令
Vue.directive('has', {
 bind: function(el, binding) {
 if (!Vue.prototype.$_has(binding.value)) {
  el.parentNode.removeChild(el);
 }
 }
});

自定义指令内部仍然是调用全局验证方法,但优点在于只会在元素初始化时执行一次,多数情况下都应该使用自定义指令实现视图控制。

请求控制

请求控制是利用axios拦截器实现的,目的是将越权请求在前端拦截掉,原理是在请求拦截器中判断本次请求是否符合用户权限,以决定是否拦截。

普通请求的判断很容易,遍历后端返回的的资源权限格式,直接判断request.method和request.url是否吻合就可以了,对于带参数的url需要使用通配符,这里需要根据项目需求前后端协商一致,约定好通配符格式后,拦截器中要先将带参数的url处理成约定格式,再判断权限,方案中已经实现了以下两种通配符格式:

1. 格式:/resources/:id
 示例:/resources/1
 url: /resources/**
 解释:一个名词后跟一个参数,参数通常表示名词的id

2. 格式:/store/:id/member
 示例:/store/1/member
 url:/store/*/member
 解释:两个名词之间夹带一个参数,参数通常表示第一个名词的id

对于第一种格式需要注意的是,如果你要发起一个url为"/aaa/bbb"的请求,默认会被处理成"/aaa/**"进行权限校验,如果这里的”bbb”并不是参数而是url的一部分,那么你需要将url改成"/aaa/bbb/",在最后加一个”/“表示该url不需要转化格式。

拦截器的具体实现见App.vue中的setInterceptor()方法。

如果你的项目还需要其他的通配符格式,只需要在拦截器中实现对应的检测和转化方法就可以了。

演示及说明

演示说明:

DEMO项目中演示了动态菜单、动态路由、按钮权限、请求拦截。

演示项目后端由rap2生成mock数据,登录请求通常应该是POST方式,但因为rap2的编程模式无法获取到非GET的请求参数,因此只能用GET方式登录,实际项目中不建议仿效;

另外登录后获取权限的接口本来不需要携带额外参数,后端可以根据请求头携带的token信息实现用户鉴权,但因为rap2的编程模式获取不到headers数据,因此只能增加一个”Authorization”参数用于生成模拟数据。

测试账号:

1. username: root
 password: 任意
2. username: client
 password: 任意

http://www.bkjia.com/Javascript/1235152.htmlwww.bkjia.comtruehttp://www.bkjia.com/Javascript/1235152.htmlTechArticleVue2.0用户权限控制解决方案,vue2.0权限控制 Vue-Access-Control是一套基于Vue/Vue-Router/axios 实现的前端用户权限控制解决方案,通过对路由、视图...

记得当年面试的时候,面试官问我,前端怎么做权限控制,咱也不太会这个,只能尴尬回答道:“都是老大搭的架子,我只负责写业务模块代码”。如今自己也做了很多项目了,觉得有必有对前端权限控制做一个总结。

前端权限控制一直是前端必须掌握的一个知识点,一般来说稍微正规一点的后台系统肯定有权限控制。当然还是那句老话,前端本来就是不安全的,真正的安全还是需要后端兄弟去把关,所以后端也必须按做权限控制!我们前端的权限校验主要的目的是过滤不该有的请求和操作,减少服务端压力。

我个人认为前端权限控制应该分为四个方面,接口权限、按钮权限,页面权限,路由权限,下面就分四个部分探讨下权限控制怎么做

1.接口权限原则

接口权限最简单,目前一般采用jwt的形式来验证,没有通过的话一般返回401 Authentication Required

登录完拿到Token,将token存起来(cookie或者ssessionStorage),每次登录的时候头部携带token就行了(axios请求拦截器实现)。

伪码实现

const {token} = login()cookie.set('token',token)axios.interceptors.request.use(config = { config.headers['token'] = cookie.get('token') return config})

2.按钮权限原则

一个页面会有新增,删除,编辑等等按钮。不同用户应该是有不同操作权限的。我们不妨定义权限码 0:不可见 1:不可编辑 2:可编辑我们提前和后端约定好按钮的名字,后端会返回一个按钮权限列表。然后我们根据权限列表使用v-if指令或者 绑定disabled属性达到相应权限效果。当然更好的最好是自己写一个自定义权限指令,实质就是根据相应权限操作dom

伪码实现

比如概览页面的编辑按钮 名字先和后端定义好叫做overview-edit

// overviwe.vue overview是概览页面的路由名...button v-auth='edit'...//util.js 全局注册自定义指令Vue.directive('auth', { inserted: function (el, binding, vnode) { const optName = binding.arg const authName = `${routeName}-${optName}` //这里根据路由名和操作类型拼出按钮名 overview-edit const btnAuthList = store.state.auth.btnAuthList if (btnAuthList[authName]===0) { // 按钮权限为0则移除dom el.parentNode.removeChild(el) } else if (btnAuthList[authName]===1) { // 按钮权限为1则禁用按钮 vnode.componentInstance.disabled = true } }})// 登录的时候接受按钮权限并存在vuex里面const {btnAuthList} = login()vuex.state.btnAuthList = btnAuthList

3.页面权限(菜单权限)

个人认为页面权限实际上就是菜单权限,如果说我们没有去某个页面的导航菜单,实际上就是没有去那个页面的权限了,所以说页面权限的实际就是菜单权限。

原则

一句话,获取菜单权限列表,动态递归生成菜单

本文由10bet发布于Web前端,转载请注明出处:前端面试:权限控制

关键词:

上一篇:没有了

下一篇:没有了

最火资讯