# 权限控制

权限控制是My框架的重要组成模块,提供了接口请求拦截、路由守卫、视图控制等全方位的细粒度的鉴权配置。

# 如何工作?

在启动Vue应用的同时,会实例化一个Access实例,在后续操作过程中,调用$access.login({roles, can, token}})$access.setRole(roles)或使用$access.setCan(can)再重新设置角色及权限。当然这些都是响应式的,也就是说,你的视图会随着以上变更发生变化。

# 功能点

  • 鉴权: Access 提供了基本的鉴权,如:$access.is('角色')$access.has('能力')$access.isLogin()。 通常用在对视图的控制。
  • 请求拦截: 可以对发起的请求在请求头统一注入token, 统一判断请求响应授权情况跳转。
  • 路由守卫:根据路由配置的中间件处理路由页面进入条件,可以方便在路由中实现鉴权。

# 配置

Access可通过 @/config.js 做个性配置或功能的开启、关闭。如:

import {set as setConfig} from '$ui/config'

setConfig({
  // 权限控制实例化配置 
  access: {
    // 启用请求权限控制
    axios: true,
    // 启用路由权限控制
    router: true,
    // 缓存存储方式 session 或 local
    storage: 'session',
    // 登录页面路径
    loginPath: '/login',
    // 权限不足页面路径
    authorizePath: '/403'
  }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

完整的配置项包括

export default {
  // 缓存key
  cacheKey: '__MY_ACCESS__',

  // 缓存存储方式 session 或 local
  storage: 'session',

  // 启用请求拦截
  axios: true,

  // 登录页面路径
  loginPath: '/login',

  // 登录页面是路由,false表示url模式
  loginPathIsRoute: true,

  // 权限不足页面路径
  authorizePath: '/403',

  // 权限不足页面是路由
  authorizePathIsRoute: true,

  // token注入请求头的名称
  authorization: 'Authorization',

  // 请求拦截函数,axios=true 有效
  request: null,

  // 请求响应成功拦截函数,axios=true 有效
  response: null,

  // 请求响应失败拦截函数,axios=true 有效
  responseFail: null,

  // 启用路由守卫
  router: true,

  // VueRouter实例
  $router: null,

  // Vuex实例
  $store: null,

  // 进入路由预处理函数,在进入路页面前进行某些处理,例如单点登录,必须返回Promise
  preprocess: null,

  // 前置路由守卫
  beforeEach: null,

  // 后置路由守卫
  afterEach: null,

  // 中间件名称分隔符
  nameSplit: ':',

  // 中间件参数个数分隔符
  paramSplit: ',',

  // 中间件参数值列表分隔符
  argSplit: '|',

  // can中间件的角色分隔符
  canRoleSplit: '.',

  // 权限层级分隔
  canParentSplit: '#',

  // 路由数组判断的字段
  routerListProp: 'path', 

  // 路由加载进度控制
  progress: null
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

# 实例方法

# $access.login(data)

写入登录信息,通常在用户登录成功时调用,写入角色、权限编码、token等用户信息。参数对象data选项

{
  roles: Array, // 角色列表
  can: Array, // 权限编码列表
  token: String // 登录token
  routerList: Array // 动态路由表
}
1
2
3
4
5
6

其中 roles 与 can 必须要有一个,即可以用角色或权限编码鉴权,或两者同时使用。 routerList 为后端返回的带权限的路由列表。 可以传入其他自定义的数据项。

# $access.get()

获取用户权限数据, 即 $access.login(data) 传入的数据

# $access.isLogin()

用户登录鉴权,判断当前用户是否已经登录

# $access.is(role, required)

用户角色鉴权,验证用户是否匹配该角色

  /**
   * 鉴权,是否有该角色
   * @param {string|string[]} role 角色或角色数组
   * @param {boolean} [required=false] 是否全匹配
   * @return {boolean}
   */
  is(role, required) {
    const roles = this.vm.access.roles
    if (!roles) return false
    
    const matches = [].concat(role)
    return required
      ? matches.every(n => roles.includes(n))
      : matches.some(n => roles.includes(n))
  }
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# $access.has(can, required)

用户权限编码鉴权,验证是否有改编码的权限

  /**
   * 鉴权,是否有该权限
   * @param {string|string[]} can 权限编码或编码数组
   * @param {boolean} [required=false] 是否全匹配
   * @return {boolean}
   */
  has(can, required) {
    const canArray = this.vm.access.can
    if (!canArray) return false
    
    const matches = [].concat(can)
    return required
      ? matches.every(n => canArray.includes(n) || this.match(n))
      : matches.some(n => canArray.includes(n) || this.match(n))
  }
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# $access.match(code)

用户权限编码父级鉴权,验证是否有该父级的编码

  /**
   * 匹配是否存在父级权限
   * @param {string} code 权限编码
   * @return {boolean}
   *
   * @example
   * match('menu') // 查询是否有 menu# 开头的权限编码, #是默认层级分隔符
   *
   */
  match(code) {
    const canArray = this.vm.access.can
    if (!canArray) return false
    return canArray.some(item => item.startsWith(`${code}${this.options.canParentSplit}`))
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# $access.logout()

注销登录,清空登录信息,并跳转到登录页面

# 视图鉴权

在模板使用v-if v-show 指令 或按钮的disabled属性控制,如:

<template>
  <el-button v-if="$access.is('admin')">按钮</el-button>
  <el-button v-if="$access.has('update')">按钮</el-button>
  <el-button :disabled="!$access.has('update')">按钮</el-button>
</template>

1
2
3
4
5
6

# 路由守卫

# 使用路由中间件功能

Access 默认提供了登录,鉴权中间件,使用路由相关功能,也比较简单。只需要在路由中meta中,添加access字段,是数组类型。如:

export default function ({get}) {
  return [
    {
      path: '/',
      component: get('index'),
      meta: {
        access: ['login', 'can:add|update']
      }
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11

# 中间件运行及原理

Access路由中间件是基于vue-router的全局钩子beforeEachafterEach。 与路由自带钩子的运行模式不同,路由中间件,是顺序执行的,前面中间件只有执行成功后,后续的中间件才可以继续执行。内部执行是基于 ui/utils/queue

# 中间件语法

Access 中内置了四个中间件,分别为:login、role、can、router

中间件使用,例如格式如下:

name:args,args....
1
  • name: 中间件名称
  • args: 传给中间件的参数,多个参数以,隔开

示例:

access: ['login']  // 需要登录访问
access: ['login', 'router']  // 需要登录访问, 且当前路由路径存在于access中存的routerList(后端返回权限路由表)时
access: ['login', 'role:administrator'] // 需要登录,并且登录的用户角色是administrator
access: ['login', 'role:administrator|teacher'] // 需要登录,并且登录的用户角色是administrator或者是teacher
access: ['login', 'role:administrator|teacher,true'] // 需要登录,并且登录的用户角色是administrator并且是teacher
access: ['login', 'can:add|update']  // 需要登录访问, 权限编码包含add 或 update
1
2
3
4
5
6

can中间件,通用是用来判断是否拥有某个权限或某一类权限,当然,也支持角色的判断。实际调用时,其实是通过$access上的can方法实现。示例如下

access: ['login', 'can:update_article'],    // 有更新文章的权限
access: ['login', 'can:update_article|delete_article'],   // 有更新文章或删除文件的权限
access: ['login', 'can:update_article|delete_article,true'], // 同时有更新及删除文章权限
1
2
3

有时,可能需要判断用户同时有某个角色或某些权限,组件role中件虽然可以实现,但是一个can中间件实现会更加简单。如下:

access: ['login', 'can:admin.update_article'],  // 拥有管理员身份,且有update_article的权限
access: ['login', 'can:admin.*|delete_article'], // 是管理员或有删除文章权限
1
2

# 自动创建路由配置写法

在vue单文件顶部写上

---
access:
- login
- router
- role:admin
---
1
2
3
4
5
6

# 自定义路由守卫

如不想使用Access提供的中间件守卫功能,可以自定义 beforeEachafterEach ,在配置中文件修改

import {set as setConfig} from '$ui/config'

// 写入运行时配置
setConfig({
  
  access: {    
    // 启用路由权限控制
    router: true,
    
    beforeEach: (to, from, next) => {
      // 逻辑实现
      next()
    },
    
    afterEach: (to, from) => {}
    
  },
  mock: {
    timeout: '200-500'
  }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 请求拦截

在token的模式,Access 在每个请求的请求头注入 Authorization,通过config.js对相关配置进行更改。

如需要特殊拦截功能,可以配置

  
  // 请求拦截函数,axios=true 有效
  request: ({access}, options, config) => {
    // 在这里实现对请求前的处理
    return config
  },
  
  // 请求响应成功拦截函数,axios=true 有效
  response: ({$router}, options, res) => {
    // 在这里实现响应后的处理
    return res
  },
  
  // 请求响应失败拦截函数,axios=true 有效
  responseFail: null,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15