如果仅从能够修改服务器中数据库中的数据层⾯上讲,确实只在后端做控制就⾜够了, 那为什么越来越多的项⽬也进⾏了前端权限的控制, 主要有这⼏⽅⾯的好处:

  • 降低⾮法操作的可能性
  • 尽可能排除不必要请求,减轻服务器压⼒
  • 提⾼⽤户体验

# 一、菜单栏控制

  在登录请求中, 会得到权限数据, 当然, 这个需要后端返回数据的⽀持. 前端根据权限数据, 展示对应的菜 单.点击菜单,才能查看相关的界⾯.

# 1、login 接口要求

  在登录后获取的数据:

除了有用户基本信息外,还要有 token、rights 两个关键字段。

  • 返回的数据示例
{
  "data": {
    "id": 500,
    "rid": 0,
    "username": "admin",
    "mobile": "13999999999",
    "email": "123999@qq.com",
    "token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1M TI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.eGrsrvwHmtPsO9r_ pxHIQ5i5L1kX9RX444uwnRGaIM"
  },
  "rights": [
    {
      "id": 103,
      "authName": "⻆⾊管理",
      "icon": "icon-tijikongjian",
      "children": [
        {
          "id": 111,
          "authName": "⻆⾊列表",
          "path": "roles",
          "rights": ["view", "edit", "add", "delete"]
        }
      ]
    },
    {
      "id": 101,
      "authName": "商品管理",
      "icon": "icon-shangpin",
      "children": [
        {
          "id": 104,
          "authName": "商品列表",
          "path": "goods",
          "rights": ["view", "edit", "add", "delete"]
        },
        {
          "id": 121,
          "authName": "商品分类",
          "path": "categories",
          "rights": ["view", "edit", "add", "delete"]
        }
      ]
    }
  ],
  "meta": {
    "msg": "登录成功",
    "status": 200
  }
}

# 2、持久化存储

  对用户信息、token 等数据进行持久化存储。

menu_loginout(){
  // 1、清除sessionStorage中的数据
  // 要么:(大量数据时)
  sessionStorage.clear()
  // 或者:(少量数据时)
  this.$store.commit('setToken','')
  this.$store.commit('setUserInfo','')

  // 2、删除vuex中的数据(F11刷新)
  window.location.reload()

  this.$router.push('/login')
}

详情:vuex 进阶

# 二、页面访问权限

  如果⽤户没有登录,⼿动在地址栏敲⼊管理界⾯的地址, 则需要跳转到登录界⾯

  如果⽤户已经登录, 可是⼿动敲⼊⾮权限内的地址, 则需要跳转 404 界⾯

# 1、导航守卫

  • route / index.js
const whiteList = ['/login', '/register']

router.beforeEach((to, from, next) => {
  if (whiteList.includes(to.path)) {
    next()
  } else {
    const token = sessionStorage.getItem('token')
    if (!token) {
      next('/login')
    } else {
      next()
    }
  }
})
  • 登录成功时
// 方式1
// this.$store.commit('updateToken', res.token)
// 方式2
this.updateToken(res.token)
// 方式3
// sessionStorage.setItem('token',res.token)

this.$router.push('/layout')

# 2、动态路由

  • router /index.js
routes: [
  {
    path: '/home',
    component: Home,
    redirect: '/welcome',
    children: [
      { path: '/welcome', component: Welcome }
      // { path: '/users', component: Users },
      // { path: '/roles', component: Roles },
      // { path: '/goods', component: GoodsList },
      // { path: '/categories', component: GoodsCate }
    ]
  },
  {
    path: '*',
    component: NotFound
  }
]

// 路由规则
const userRule = { path: '/users', component: Users }
const roleRule = { path: '/roles', component: Roles }
const goodsRule = { path: '/goods', component: GoodsList }
const categoryRule = { path: '/categories', component: GoodsCate }
import $store from '@/store/index'

// 登录请求path路由 与 路由规则 的映射🚩
const ruleMapping = {
  users: userRule,
  roles: roleRule,
  goods: goodsRule,
  categories: categoryRule
}

// 根据用户权限,设置动态路由规则
export function initDynamicRoutes() {
  console.log(router)
  // 1、获取路由数组
  const currentRoutes = router.options.routes

  const rightList = $store.state.rightList
  rightList.forEach((item) => {
    item.children.forEach((item) => {
      // 2、动态添加二级路由(自己查看下标)
      const temp = ruleMapping[item.path]
      currentRoutes[2].children.push(temp)
    })
  })

  // 2、正式重置路由
  router.addRoutes(currentRoutes)
}
  • 登录成功时
import { initDynamicRoutes } from '@/router/index.js'

// ……

login() {
  // ……
  initDynamicRoutes()
}
  • 根组件防止数据丢失
import { initDynamicRoutes } from '@/router/index.js'
export default {
  name: 'app',
  created() {
    initDynamicRoutes()
  }
}

# 三、按钮操作权限

  在某个菜单的界⾯中, 还得根据权限数据, 展示出可进⾏操作的按钮, ⽐如删除,修改,增加

# 1、自定义指令

  • main.js
import './utils/permission.js'
  • router / index.js
// 根据用户权限,设置动态路由规则
export function initDynamicRoutes() {
  const currentRoutes = router.options.routes

  const rightList = $store.state.rightList
  rightList.forEach((item) => {
    item.children.forEach((item) => {
      // item 二级权限
      const temp = ruleMapping[item.path]
      temp.meta = item.rights // 利用路由元信息附加上权限信息(备用👏)

      currentRoutes[2].children.push(temp)
    })
  })

  router.addRoutes(currentRoutes)
}
  • utils / permission.js
import Vue from 'vue'
import router from '@/router.js'

// 自定义全局指定
Vue.directive('permission', {
  inserted: function (el, binding) {
    // console.log(el)
    // console.log(binding)

    const action = binding.value.action
    const effect = binding.value.effect

    // 根据用户权限操作element按钮(view、edit、add、delete)
    const currentRight = router.currentRoute.meta
    if (currentRight) {
      // 1、若没有有权限
      if (currentRight.indexOf(action) === -1) {
        // 2、要禁用还是移除
        if (effect === 'disabled') {
          el.disabled = true
          el.classList.add('is-disabled') //使用class禁用element按钮
        } else {
          el.parentNode.removeChild(el) // 删除element按钮
        }
      }
    }
  }
})
  • 使用示例
<!-- 添加按钮,禁用状态 -->
<el-button
  type="primary"
  @click="addDialogVisible = true"
  v-permission="{action: 'add',effect: 'disabled'}"
  >添加用户</el-button
>

# 四、API 访问权限

  如果⽤户通过⾮常规操作, ⽐如通过浏览器调试⼯具将某些禁⽤的按钮变成启⽤状态, 此时发的请求, 也应该被前端所拦截

# 1、axios 拦截器

  • utils / request.js
import $router from '../router'

// 引入element-ui弹窗
import { Message } from 'element-ui'

// 请求类型 与 操作类型 的映射
const actionMapping = {
  get: 'view',
  post: 'add',
  put: 'edit',
  delete: 'delete'
}
  • 请求拦截器(身份过期校验)
axios.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么

    const currentUrl = config.url
    const whiteList = ['/login', '/register']

    if (whiteList.includes(currentUrl)) {
      // 1、token验证
      config.headers.Authorization = $store.state.token
      // config.headers.Authorization = sessionStorage.getItem('token')

      // 2、权限验证✨
      const method = config.method
      const action = actionMapping[method]

      const currentRight = router.currentRoute.meta
      if (currentRight && currentRight.indexOf(action) === -1) {
        // 没有权限
        // alert('没有权限')
        Message.error('没有权限')
        return Promise.reject(new Error('没有权限'))
      }
    }

    return config
  },
  function (error) {
    return Promise.reject(error)
  }
)
  • 响应拦截器
// (请求超时或被篡改)
axios.interceptors.response.use(
  function (response) {
    return response
  },
  function (error) {
    if (error.response.status === 401) {
      // 1、清除sessionStorage中的数据
      // 要么:(大量数据时)
      sessionStorage.clear()
      // 或者:(少量数据时)
      $store.commit('setToken', '')
      $store.commit('setUserInfo', '')

      // 2、删除vuex中的数据(F11刷新)
      window.location.reload()

      $router.push('/login')

      // 弹窗提示
      Message.error('用户身份以过期!!')
    }

    return Promise.reject(error)
  }
)
更新于 : 7/8/2024, 10:21:14 AM