|

Vue单页面应用如何进行权限管理

Vue 之类的前端框架的出现让 SPA 更加方便,同时也带来了一些小问题,例如一个拥有用户权限的应用,如何根据用户的不同权限来展示页面和功能?文本主要整理讨论与权限管理有关的问题及其解决方案。

简述

在当前的工业互联网项目中,智能表计的数据获取到以后需要整理分析,然后为不同角色的用户来指定不同的浏览规则,于是就需要前进的组件和路由进行权限管理。

首先,我们应该先弄懂两层含义,第一是对组件的权限管理,另一种是对路由页面的权限管理。页面路由的权限管理比较好理解,类似于早期的管理员页面,不同角色有自己可以浏览的页面,这种情况下,页面是一个完整的功能单位,此时权限可以基于页面来划分。而组件权限就有所不同了,这类需求虽不多,但在开发中还是有碰到,往往是一个页面中,不同的用户可以操作的功能有所不同。

组件权限举个简单的例子,有一个设备列表,管理员可以对这个列表进行增删,但是另一个用户组的权限则只能浏览,这些都可以在一个页面中,但是功能却有所不同。

获取权限

获取权限可以通过用户登录来完成,可以分如下几步:

  • 用户通过前端页面完成登录,接口返回用户的 id 和所在组;
  • 应用根据用户的 id 和用户组,获取用户权限和组权限;
  • 应用将用户权限和组权限合并(用户权限优先于组权限),生成路由权限表和组件权限表;
  • 前端根据路由权限表加载用户的默认页面。

从上面的过程可以知道几点:

  • 用户注册登录页面可以与应用分开,如果站点应用较多,也可以使用全局的 passport 组件来登录;
  • 由于预先不知道用户角色,所以前端应用可以按照最小体积来打包,待获取到用户信息后再异步加载;
  • 组件同上。

根据上面的方案,又要有两种情况,一种是使用第三方UI组件,另一种是使用自己开发组件,那么这种了情况下组件的权限又该如何获得呢?

下面我将一一解决上面的问题。

路由权限管理

路由权限是比较好理解的,也是比较好实现的。

例如,通过用户登录,我们可以得知当前用户的 id 和用户组,所以合并这两个权限以后,就会得到一个新的路由组,这个组里的路由按理都是可以访问的,不再这个组里的路由就是不能访问的,不能访问的路由便需要拦截。

在 Vue 中,对路由的拦截可以在 router.beforEach 中进行。

例如,如果应用必须要求用户先登录,那么可以通过检查 store 中的 state 数据来判断,未登录时则通过路由守卫跳转至登录页面。

假设通过 Vuex 创建了一个状态管理的 store,store.state 有以下定义:

const store = new Vuex.Store({
  state: {
    userinfo: {
      name: "Tom",
      nickname: 'Tom',
      groupid: 2,
      sessionid: '1c24g5b67j7l8'
    },
    routes: [
      '/',
      '/home',
      '/about'
    ]
  },
  mutations: {

  },
  getters: {
    userinfo(state) {
      return state.userinfo;
    },
    routes(state) {
      return state.routes;
    }
  }
})

上面代码的 routes 是可路由的列表,这个列表又用户登录后获取的权限合并而来。

那么,在路由守卫中可以这样做权限判断:

router.beforeEach((to, from, next) => {
  // 未登录,跳转到登录页
  if (to.fullPath !== '/login' && !store.getters.userinfo) {
    next({path: '/login'})
  }
  // 已登录,则判断用户是否有权访问
  else if (store.getters.routes.indexOf(to.fullPath) > -1) {
    next()
  }
});

不在 routes 中的路由将不允许跳转。

组件权限

与路由权限不同,组件权限会稍微复杂一点,这是由于路由权限可以以页面为最小单位,但是组件却是页面的组成部分,粒度更小。

上文中有说,组件的权限可以分使用第三方的和不使用第三方的,使用第三的由于组将已经被封装好,我们是不回去改变组件源码的,所以只能对其进行扩展。而自行开发的则相对灵活一些,可以由开发者自行处理权限。

为了统一,我们这里可以统一一下,全部归为私有组件,以便增加灵活性,对于使用第三方组件的情况,我们可以通过组件包裹来变成私有组件。

例如,有 element-ui 组件如下:

<el-button type="primary">用户按钮</el-button>

通过包裹可得:

<template>
    <div class="sl-buttom">
        <el-button type="primary">用户按钮</el-button>
    </div>
</template>

然后定义组件功能:

import defaultPermisson from "@/permisson/sl-button"

export default {
  name: 'sl-buttom',
  data: () => {
    return {
      permission: {}
    }
  },
  created() {
    let userinfo = this.$store.getters.userinfo;
    let groupid = userinfo.groupid;
    let uid = userinfo.uid;
    // 设置默认组权限
    this.permission = defaultPermisson[groupid];
  },
  methods: {
    postData () {
      console.log("hello world post data");
    }
  }
}

文件夹 permisson 定义了组件的默认权限,以这个 button 为例,可以设置按钮的文字,按钮颜色,已经按钮是否可点击等。然后将权限数据赋值到组件上。

<template>
    <div class="sl-buttom">
        <el-button 
            v-show="permission.show"
            :type="permission.type || 'primary'" 
            :disabled="permission.disabled || false"
        >{{permission.name || '用户按钮'}}</el-button>
    </div>
</template>

然后我们只需要在 sl-button 权限文件中返回权限定义即可,这个权限完全可以独立定义,也可以又后台来生成,这样就可以把权限和组件分开来管理。权限文件可以这样定义:

// /permisson/sl-button.js
export default {
    default: {
        name: "默认权限",
        show: true,
        disabled: false,
        type: 'primary'
    },
    group1: {
        name: "组1权限",
        show: true,
        disabled: true,
        type: 'default'
    },
    group1: {
        name: "组1权限",
        show: false,
        disabled: true,
        type: 'default'
    }
}

以上只是一个示例。

使用场景

那么,哪些情况下需要使用权限管理呢?路由权限很好理解,不需要用户访问的页面我们不允许访问即可,那么组件权限如何在页面中体现呢?

上面我介绍的组件权限是一种实现方式,并不代码需要将权限定义的粒度划分到一个按钮上。在实际的开发中,我们往往是对具有一定功能的组件集合,或者一个大型组件才会使用权限定义,另外一个场景就是给用户显示菜单,显示功能区,那么这种情况上述的组件定义就很方便了。

一般来说,我们对一个组件可以设置的维度有:

  • 是否显示(包括显示和不显示,以及如果显示的话样式是否有定义);
  • 是否可读(是否显示一些特定数据);
  • 是否可操作(例如用户的点击操作等);

例如有一些页面我们是不希望用户访问的,那么在导航里就可以对响应的组件设置为不显示;另一些组件我们希望用户可以看到,但是仅只读,不能操作,那么就可以通过权限将其设置为想要的状态。

用户权限的同步

读者可能注意到,上面组件的权限使用的时候默认定义,那么如果有一些功能权限是这个用户所独有的,按照用户权限优先与组权限的原则,我们应该去同步获取用户权限的定义,这个时候就需要与接口服务器通信,来同步获取用户权限,我们可以通过一个 Promise 来实现。

import defaultPermisson from "@/permisson/sl-button"

export default {
  name: 'sl-buttom',
  data: () => {
    return {
      permission: {}
    }
  },
  created() {
    let userPermis = this.store.getters.userPermis;
    let userinfo = this.store.getters.userinfo;
    let groupid = userinfo.groupid;
    let uid = userinfo.uid;
    // 如果有用户权限则使用用户权限,没有用户权限则使用默认的组权限
    this.permission = userPermis.indexOf(this.name) ? userPermis[this.name] : defaultPermisson[groupid];
    // 这里异步获取用户权限,在获取成功后更新当前的权限
    this.permissionGette(uid).then(userPermissions => {
        // 通过 Vuex 提供的 store 将用户权限缓存起来
        this.store.commit('updateUserPermisson', userPermissions);
        // 更新当前的权限数据
        this.permission = userPermissions;
    })
  },
  methods: {
    postData () {
      console.log("hello world post data");
    }
  }
}

引申:如何控制权限粒度

细心的朋友可能会发现,上面组件权限的控制中,可能存在一个空窗期,即我们先给组件赋值了组权限,然后才去拉取用户权限,在用户权限返回之前,这个组件可能拥有的是组权限,如何组权限与用户权限不一样,就会造成不一致的情况。

确实,这是一个问题,所以才要谈控制权限粒度。

控制权限的粒度主要设备组件权限的控制粒度,包括:

  • 什么样的组件需要定义权限,什么样的组件不需要定义权限;
  • 什么时候拉取用户权限。

上面两个问题搞清楚后其实组件权限的问题也就比较好处理了。

首先,前面已经有介绍,不是所有的组件都需要进行权限定义,而是需要根据业务来定义。在业务上,一般权限是用来区分功能的,或者说是能力的。例如有一个权限组对某个数据可读可写,但另一个用户组却只能读不能写,那么读写就是功能的区分了。

如果这个读写操作时通过一个按钮组件来实现的,那么就是说这个组件具备拥有权限管理的必要,这个时候我们才会为这个组件定义权限。

第二个问题,何时拉取用户权限?

上面我们讨论了什么是用户权限什么是组权限,用户权限优先于组权限,如果一个大型 SPA 项目所涉及的用户权限比较多又比较复杂,我们可以在进入到当前路由页面的时候再拉取,这样的粒度会更小。如果用户权限不多,那么可以在用户登录的时候直接拉取。

总结

虽然前段可以实现视觉上的权限管理,但是最后的关卡还是在后端,尤其功能上的权限。功能权限一把会与组件绑定,所以后端需要设置后对功能接口的拦截,防止用户恶意操作。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注