权限管理

一个 CMS 系统,权限管理是比较基础的也是很重要的一块。在前端做权限管理,一般有两种做法:

  • 视图层的访问权限
  • API 的操作权限

视图控制

视图控制的目标是根据当前用户权限决定用户能访问哪些页面、操作哪些 DOM,典型场景是动态渲染菜单及控制各种操作按钮的显示隐藏。

TIP

无论是控制页面显示还是控制页面内 DOM 显示,都是在后端返回的权限列表中,具体哪个权限控制哪里,要根据具体的业务场景由前端工程师和后端工程师共同商量确定。

菜单渲染

在渲染菜单的时候,有两种不同的情况:

  • 渲染所有菜单,当用户访问不在自己权限范围内的页面时提示权限不足。
  • 只显示当前用户能访问的菜单,如果用户通过 URL 强制访问,提示用户无此权限并退回到当前页面。为了提高用户体验,Lin 框架使用第二种方式来渲染菜单,

路由配置项里的 right 属性为该路由的权限配置,该属性的值类型为数组,也就是说一个页面可以配置多个权限,而登录用户至少需要拥有其中的一种权限,左侧菜单才会把该路由的路口渲染出来;如果不配置 right 属性,表示该页面是公共页面,所有用户都可以访问。

比如:

  1. const homeRouter = [
  2. {
  3. title: '林间有风',
  4. type: 'view',
  5. name: Symbol('about'),
  6. route: '/about',
  7. filePath: 'views/about/About.vue',
  8. inNav: true,
  9. icon: 'iconfont icon-iconset0103',
  10. order: 0,
  11. },
  12. {
  13. title: '日志管理',
  14. type: 'view',
  15. name: Symbol('log'),
  16. route: '/log',
  17. filePath: 'views/log/Log.vue',
  18. inNav: true,
  19. icon: 'iconfont icon-rizhiguanli',
  20. order: 1,
  21. right: ['查询所有日志'],
  22. },
  23. ]

所有用户都可以访问 about 页面,而 log 页面需要用户拥有 查询所有日志 的权限才能访问。

这部分权限筛选实现: src/store/getters.js

权限的筛选是由上及下,如果父路由没有权限,那么由此往下整个 分支树 都将被 shaking 掉。

如果用户试图通过 URL 强制访问没有权限的路由,Lin 框架也在全局路由里做了拦截:src/router/index.js 确保了用户在正常的操作过程中,无法访问与其权限不符合的页面。

DOM 元素渲染

除了控制页面层级的访问,Lin 框架还提供了 v-auth 指令,在可以发出请求的 DOM 上添加该指令,根据用户权限,控制该 DOM 的隐藏与显示

我们以能否搜索日志为例

  1. <button v-auth="['修改信息','修改密码']">编辑</button>
  2. // 或者
  3. <div v-auth="'搜索日志'">
  4. // 具体展示代码省略
  5. </div>

如果一个 DOM 对应多个 API,比如当前登录的管理员想要修改自己的用户信息,但是没有修改密码的权限,那么编辑按钮显示。如果一个 DOM 对应一个 API,那么直接传入字符串即可。

权限管理 - 图1

如果想要 DOM 显示,但只是不能操作状态,我们还提供了这样的指定:

<button v-auth="{ auth: '删除图书', type: 'disabled'}">删除</button>

权限管理 - 图2

API 控制

视图层的权限控制实质上就是控制显示,过滤掉无法使用的页面及 DOM 节点,让用户体验变得更加友好。但这并不安全,比如用户可以用测试工具模拟 API 发出请求,同样可以拿到想要的数据,所以真正能实现安全的是接口,没有通过后端验证的 API 请求是无法到数据的。 在 Lin 框架里,当用户登录成功后,后端会返回 token,该 token 会自动添加到 axios 的请求头 Authorization,这样用户发出的每一个 API 请求才能通过后端验证拿到数据。

位置: src/lin/utils/http.js

// 添加一个请求拦截器
http.interceptors.request.use(
  (requestConfig) => {
    if (requestConfig.url === 'cms/user/refresh') {
      const refreshToken = getToken('refresh_token')
      if (refreshToken) {
        requestConfig.headers.Authorization = refreshToken
        return requestConfig
      }
    } else {
      // 有access_token
      const accessToken = getToken('access_token')
      if (accessToken) {
        requestConfig.headers.Authorization = accessToken
        return requestConfig
      }
    }
    return requestConfig
  },
  error => Promise.reject(error),
)

更多关于 token 添加、验证、刷新实现原理参考异常机制

小节

在 Lin 框架里,我们实现了对每个页面、每一个 DOM 节点进行权限管理,在数据请求接口添加了 token 验证,通过验证后才能从后台拿回数据。开发者只需要经过简单的配置,就能拥有一个带权限管理的 CMS 系统。