Управление разрешением Vue (проверка маршрута)

Vue.js

Ниже описаны два метода контроля разрешений:

  • Метаинформация о маршрутизации (мета)
  • Динамическая загрузка меню и маршрутов (addRoutes)

Метаинформация о маршрутизации (мета)

Если у веб-сайта разные роли, напримерадминистратора такжеобычный пользователь, страницы, для доступа к которым требуются разные роли, отличаются

В это время мы можемПоместить все страницы в таблицу маршрутизации,если толькоОпределить права доступа при доступе. Если у вас есть разрешение, разрешите доступ, если у вас нет разрешения, запретите доступ и перейдите на страницу 404

vue-routerМетаинформация была предоставлена ​​при построении маршрутаmetaЧтобы настроить интерфейс, мы можем добавить разрешения, соответствующие маршруту, в метаинформацию, а затем проверить соответствующие разрешения в блоке маршрутизации, чтобы контролировать переход маршрута.

доступен на каждом маршрутеmetaВ атрибуте добавьте роль, которая может получить доступ к маршруту доrolesвнутри. После того, как пользователь входит в систему каждый раз, роль пользователя возвращается. Потом при заходе на страницу проставить маршрутmetaАтрибут сравнивается с ролью пользователя, если роль пользователя указана в маршрутизации.rolesЕсли он есть, к нему можно получить доступ, а если его нет, доступ запрещен.

Пример кода 1:

Информация о маршрутизации:

routes: [
    {
        path: '/login',
        name: 'login',
        meta: {
            roles: ['admin', 'user']
        },
        component: () => import('../components/Login.vue')
    },
    {
        path: 'home',
        name: 'home',
        meta: {
            roles: ['admin']
        },
        component: () => import('../views/Home.vue')
    },
]

Управление страницей:

//假设有两种角色:admin 和 user  
//从后台获取的用户角色
const role = 'user'
//当进入一个页面是会触发导航守卫 router.beforeEach 事件
router.beforeEach((to,from,next)=>{
	if(to.meta.roles.includes(role)){
		next()	//放行
	}esle{
		next({path:"/404"})	//跳到404页面
	}
})

Пример кода 2

Конечно, вы также можете использовать один из следующих методов:

// router.js
// 路由表元信息
[
  {
    path: '',
    redirect: '/home'
  },
  {
    path: '/home',
    meta: {
      title: 'Home',
      icon: 'home'
    }
  },
  {
    path: '/userCenter',
    meta: {
      title: '个人中心',
      requireAuth: true // 在需要登录的路由的meta中添加响应的权限标识
    }
  }
]

// 在守卫中访问元信息
router.beforeEach (to, from, next) {
  let flag = to.matched.some(record=>record.meta.requireAuth);
  //console.log(flag);  //可自己打印出来看一下
}

Вы можете добавить этот идентификатор разрешения в несколько маршрутов для достижения цели контроля.

Пока вы переключаете страницы, вам нужно проверить, есть ли у вас это разрешение, чтобы вы могли использовать самый большой маршрутmain.jsСредняя конфигурация

хранить информацию

Как правило, после входа пользователя информация об аутентификации пользователя будет храниться локально.token,cookieподождите, здесь мы используемtoken.

пользователяtokenСохранитьlocalStorage, а информация о пользователе хранится в памятиstoreсередина. Это можно сделать вvuexСохраняет свойство, которое отмечает статус входа пользователя вauth, что удобно для контроля разрешений.

пример кода

// store.js
{
  state: {
    token: window.localStorage.getItem('token'),
    auth: false,
    userInfo: {}
  },
  mutations: {
    setToken (state, token) {
      state.token = token
      window.localStorage.setItem('token', token)
    },
    clearToken (state) {
      state.token = ''
      window.localStorage.setItem('token', '')
    },
    setUserInfo (state, userInfo) {
      state.userInfo = userInfo
      state.auth = true // 获取到用户信息的同时将auth标记为true,当然也可以直接判断userInfo
    }
  },
  actions: {
    async getUserInfo (ctx, token) {
      return fetchUserInfo(token).then(response => {
        if (response.code === 200) {
          ctx.commit('setUserInfo', response.data)
        }
        return response
      })
    },
    async login (ctx, account) {
      return login(account).then(response => {
        if (response.code === 200) {
          ctx.commit('setUserInfo', response.data.userInfo)
          ctx.commit('setToken', response.data.token)
        }
      })
    }
  }
}

После написания таблицы маршрутизации и vuex установите глобальную защиту для всех маршрутов, проверьте разрешения перед вводом маршрута и перейдите к соответствующему маршруту.

// router.js
router.beforeEach(async (to, from, next) => {
  if (to.matched.some(record => record.meta.requireAuth)) { // 检查是否需要登录权限
    if (!store.state.auth) { // 检查是否已登录
      if (store.state.token) { // 未登录,但是有token,获取用户信息
        try {
          const data = await store.dispatch('getUserInfo', store.state.token)
          if (data.code === 200) {
            next()
          } else {
            window.alert('请登录')
            store.commit('clearToken')
            next({ name: 'Login' })
          }
        } catch (err) {
          window.alert('请登录')
          store.commit('clearToken')
          next({ name: 'Login' })
        }
      } else {
        window.alert('请登录')
        next({ name: 'Login' })
      }
    } else {
      next()
    }
  } else {
    next()
  }
})

Вышеупомянутый метод основан наjwtМетод аутентификации, информация о пользователе не сохраняется локально, а только сохраняетсяtoken, когда пользователь обновляет или повторно открывает веб-страницу и входит на страницу, на которой необходимо войти в систему, он попытается запросить информацию о пользователе.Эта операция выполняется только один раз в течение всего процесса посещения до тех пор, пока она не будет обновлена ​​или повторно открыта, что очень полезно для последующей разработки, обслуживания и поддержки расширения приложения.

Динамическая загрузка меню и маршрутов (addRoutes)

Иногда в целях безопасности нам необходимо динамически добавлять меню и таблицы маршрутизации в соответствии с разрешениями или атрибутами пользователя, которые могут настраивать пользовательские функции.vue-routerпри условииaddRoutes()метод, вы можете динамически регистрировать маршруты,Обратите внимание, что динамически добавлять маршруты в таблицу маршрутизацииpushМаршруты, поскольку маршруты сопоставляются по порядку, такие маршруты, как страницы 404, необходимо размещать в конце динамического добавления.

пример кода

// store.js
// 将需要动态注册的路由提取到vuex中
const dynamicRoutes = [
  {
    path: '/manage',
    name: 'Manage',
    meta: {
      requireAuth: true
    },
    component: () => import('./views/Manage')
  },
  {
    path: '/userCenter',
    name: 'UserCenter',
    meta: {
      requireAuth: true
    },
    component: () => import('./views/UserCenter')
  }
]

существуетvuexдобавлено вuserRoutesМассив используется для хранения пользовательского меню пользователя. В setUserInfo таблица маршрутизации пользователя создается в соответствии с меню, возвращаемым серверной частью.

// store.js
setUserInfo (state, userInfo) {
  state.userInfo = userInfo
  state.auth = true // 获取到用户信息的同时将auth标记为true,当然也可以直接判断userInfo
  // 生成用户路由表
  state.userRoutes = dynamicRoutes.filter(route => {
    return userInfo.menus.some(menu => menu.name === route.name)
  })
  router.addRoutes(state.userRoutes) // 注册路由
}

Изменить рендеринг меню

// App.vue
<div id="nav">
  <router-link to="/">主页</router-link>
  <router-link to="/login">登录</router-link>
  <template v-for="(menu, index) of $store.state.userInfo.menus">
    <router-link :to="{ name: menu.name }" :key="index">{{menu.title}}</router-link>
  </template>
</div>

Полный пример динамической загрузки меню и маршрутизации

Если вы прочитали предыдущую и чувствуете, что хорошо ее понимаете, вам не нужно читать ниже, конечно, вы также можете взглянуть на этот кейс и попробовать написать его самостоятельно.

Дружеское напоминание: Этот случай может быть немного сложным, и некоторые логики трудно понять, лучше всего написать его самому, а затем медленно переваривать самостоятельно.


Итак, приступим:

Создайте новый проект, удалите ненужные файлы и код

Бэкэнд готовит данные

В каталоге src создайте новыйserver.jsфайл следующим образом:

Код:
Это связано с междоменными проблемами, подробности см. в моей статье.«Общие междоменные решения (полное)»

//Server.js

let express  = require('express');
let app = express();
//在后端配置,让所有人都可以访问api接口 其中设计到跨域问题,具体参考我的文章《常见的跨域解决方案(全)》
app.use('*', function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    //Access-Control-Allow-Headers ,可根据浏览器的F12查看,把对应的粘贴在这里就行
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    res.header('Access-Control-Allow-Methods', '*');
    res.header('Content-Type', 'application/json;charset=utf-8');
    next();
});
app.get('/role',(req,res)=>{
    res.json({
        //假如这是后端给我们的JSON数据
        menuList:[
            {pid:-1,path:'/cart',name:'购物车',id:1,auth:'cart'},
            {pid:1,path:'/cart/cart-list',name:'购物车列表',id:4,auth:'cart-list'},
            {pid:4,path:'/cart/cart-list/lottery',auth:'lottery',id:5,name:'彩票'},
            {pid:4,path:'/cart/cart-list/product',auth:'product',id:6,name:'商品'},
            {pid:-1,path:'/shop',name:'商店',id:2,auth:'shop'},
            {pid:-1,path:'/profile',name:'个人中心',id:3,auth:'profile'},
        ],
        buttonAuth:{
            edit:true, // 可编辑
        }
    })
})
//监听3000端口
app.listen(3000);

Затем проверьте, можно ли получить данные порта (используя команду node):

Используйте браузер для доступа к /role под портом 3000
Если данные доступны, порт в порядке

Затем напишите следующие соответствующие страницы с основным скелетом vue.

существуетrouter.jsнастроить маршрутизацию в

import Vue from 'vue'
import Router from 'vue-router'
//引入组件
import Home from "./views/Home.vue"

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  //配置路由
  routes: [
    {
      //访问'/'时,重定向到home页面
      path:'/',
      redirect:'/home'
    },
    {
      path:'/home',
      name:'home',
      component:Home
    },
    {
      path:'/cart',
      name:'cart',
      //使用懒加载,当使用这个组件的时候再加载资源,当组件资源较大时,不建议使用,可能会出现白屏现象
      //而且最好使用绝对路径,@是绝对路径的意思,相当于src下
      component:()=>import('@/components/menu/cart.vue'),
      //配置子路由
      children:[
        {
          //当配置子路由时,最好不要在前面加'/',比如:'/cart-list'
          path:'cart-list',
          name:'cart-list',
          component:()=>import('@/components/menu/cart-list.vue'),
          //配置子路由
          children:[
            {
              path:'lottery',
              name:'lottery',
              component:()=>import('@/components/menu/lottery.vue')
            },
            {
              path:'product',
              name:'product',
              component:()=>import('@/components/menu/product.vue')
            }
          ]
        }
      ]
    },
    {
      path:'/profile',
      name:'profile',
      component:()=>import('@/components/menu/profile.vue')
    },
    {
      path:'/shop',
      name:'shop',
      component:()=>import('@/components/menu/shop.vue')
    },
    {
      path:'*',
      component:()=>import('@/views/404.vue')
    }
  ]
})

Введите маршрут в адресную строку браузера, проверьте, должно быть все в порядке, здесь нет карты

После завершения настройки маршрутизации данные, возвращаемые нам бэкендом, показаны на рисунке:

Нам нужно преобразовать приведенные выше данные в нужный нам формат данных и сохранить их в vuex (что связано с асинхронными проблемами, подробности см. в моей статье).«Асинхронное решение на JS»),следующим образом:

//store.js
//重难点代码  很难理解 需要多看几遍慢慢理解
let formatMenuList = (menuList) => {
  function r(pid) {
    //filter过滤数组,返回一个满足要求的数组
    return menuList.filter(menu => {
      //格式化菜单变成我们需要的结果
      if (menu.pid === pid) {        
        let children = r(menu.id);
        menu.children = children.length ? children : null;
        return true;
      }
    })
  }
  return r(-1);
}

actions: {
    //异步  async await 参考我的文章<异步解决方案>
    async getMenuList({ commit }) {
      //去server.js(后端api)获取数据
      let { data } = await axios.get('http://localhost:3000/role');

      let menuList = data.menuList
      //把获取的数据传入上面写的方法里,转换格式
       menuList = formatMenuList(menuList)
      //配置完全局路由(main.js)后,可自己打印出来看一下
      // console.log(menuList);
    },
  }

Для формата данных, который необходимо сохранить, возьмите дерево данных, которое я написал ранее для всеобщего обозрения, просто посмотрите на структуру, а не на данные:

treeData: {
        title: "Web全栈架构师",
        children: [
          {
            title: "Java架构师"
          },
          {
            title: "JS高级",
            children: [
              {
                title: "ES6"
              },
              {
                title: "动效"
              }
            ]
          },
          {
            title: "Web全栈",
            children: [
              {
                title: "Vue训练营",
                expand: true,
                children: [
                  {
                    title: "组件化"
                  },
                  {
                    title: "源码"
                  },
                  {
                    title: "docker部署"
                  }
                ]
              },
              {
                title: "React",
                children: [
                  {
                    title: "JSX"
                  },
                  {
                    title: "虚拟DOM"
                  }
                ]
              },
              {
                title: "Node"
              }
            ]
          }
        ]
      }

затем вmain.jsДобавьте маршрутизацию (включая проблемы с навигацией, пожалуйста, обратитесь к моей статьеVue Navigation Guard (жизненный цикл маршрутизатора)) следующим образом:

//main.js
//只要页面切换就执行的钩子
//根据权限动态添加路由(我们的路由要分成两部分:一部分是有权限的,另一部分是没有权限的)
router.beforeEach(async (to,from,next)=>{
  //判断当前有没有获取过权限,如果获取过了,就不要再获取了
  if(!store.state.hasRules){
    //获取权限,调用获取权限的接口,去action中获取数据
    await store.dispatch('getMenuList')
  }else{
    //如果已经获取了权限就可以访问页面了
    next()
  }
})

Затем посетите главную страницу, эффект выглядит следующим образом:

В приведенных выше данных есть элемент авторизации, который означает, есть ли у вас это разрешение или нет. Если у вас нет этого разрешения, в это время меню будет удалено из правил маршрутизации. Отфильтруйте все автомобили следующим образом:

Затем протестируйте на главной странице следующим образом (не забудьте раскомментировать приведенный выше код console.log):

Если бэкенд данные (menuList) изменились, то обновите страницу еще раз, тогда данные изменятся динамически, я не буду здесь это демонстрировать, вы можете закомментировать и посмотреть.

Затем визуализируйте данные меню,
существуетmain.jsсерединаиспользуя элемент-интерфейс:

//main.js
//安装 element-ui 并使用
import ElementUI from "element-ui"
//引入element-ui里的样式
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)

Визуализируйте данные, которые мы только что получили, на Home:

Использование домаmapStateметод:

При рендеринге нам нуженрекурсивный компонентЧтобы динамически получать передаваемые данные и отображать их динамически, поэтому они не должны быть жестко закодированы.Пожалуйста, обратитесь к моей статье для использования рекурсивных компонентов."Рекурсивный компонент Vue (меню построения дерева)", я не буду вводить здесь слишком много, просто используйте его напрямую

добавить просмотры нижеReSubMenuкомпонент, код выглядит следующим образом:

<template>
    <el-submenu :index="data.path">
      <template slot="title">
          <router-link :to="data.path">{{data.name}}</router-link>
      </template>
      <template v-for="c in data.children">
        <ReSubMenu :key="c.auth" v-if="data.children" :data="c">
        </ReSubMenu>
        <el-menu-item :key="c.auth" v-else :index="c.path">
            {{c.name}}
        </el-menu-item>
      </template>
    </el-submenu>
</template>
<script>
export default {
    name:"ReSubMenu",
    props:{
        data:{
            type:Object,
            default:()=>({})
        }
    }
}
</script>

Затем используйте этот компонент в Home

Код в шаблоне выглядит следующим образом:

<template>
  <div class='home'>
    <!-- <router-view></router-view> -->
    <el-menu default-active="2" class="el-menu-vertical-demo" :router="true">
      <template v-for="m in menuList">
        <!-- 渲染肯定是不能写死的,所以我使用之前封装过的递归组件,具体内容参考我的文章<Vue 递归组件(构建树形菜单)> -->
        <ReSubMenu :data="m" :key="m.auth" v-if="m.children"></ReSubMenu>
        <el-menu-item v-else :key="m.auth" :index="m.path">{{m.name}}</el-menu-item>
      </template>
    </el-menu>
    <!-- <router-view></router-view> -->
  </div>
</template>

Изображение эффекта:

Таким образом, необходимая нам структура может быть динамически сгенерирована в соответствии с переданными данными.

Но если бэкэнд не вернет нам профиль, его нельзя будет остановить, следующим образом:

Так же есть доступ к профилю, а именно:

Это не сработает, потому что бэкэнд не возвращает нам профиль

Как решить?

A: Динамически добавлять маршруты в соответствии с разрешениями (наши маршруты должны быть разделены на две части, одна часть авторизована, а другая не авторизована)

Исправлятьrouter.jsКод в следующем:

import Vue from 'vue'
import Router from 'vue-router'
//引入组件
import Home from "./views/Home.vue"

Vue.use(Router)

let defaultRoutes = [
  {
    //访问'/'时,重定向到home页面
    path:'/',
    redirect:'/home'
  },
  {
    path:'/home',
    name:'home',
    component:Home
  },
  {
    path:'*',
    component:()=>import('@/views/404.vue')
  }
]

export let authRoutes = [
  {
    path:'/cart',
    name:'cart',
    //使用懒加载,当使用这个组件的时候再加载资源,当组件资源较大时,不建议使用,可能会出现白屏现象
    //而且最好使用绝对路径,@是绝对路径的意思,相当于src下
    component:()=>import('@/components/menu/cart.vue'),
    //配置子路由
    children:[
      {
        //当配置子路由时,最好不要在前面加'/',比如:'/cart-list'
        path:'cart-list',
        name:'cart-list',
        component:()=>import('@/components/menu/cart-list.vue'),
        //配置子路由
        children:[
          {
            path:'lottery',
            name:'lottery',
            component:()=>import('@/components/menu/lottery.vue')
          },
          {
            path:'product',
            name:'product',
            component:()=>import('@/components/menu/product.vue')
          }
        ]
      }
    ]
  },
  {
    path:'/profile',
    name:'profile',
    component:()=>import('@/components/menu/profile.vue')
  },
  {
    path:'/shop',
    name:'shop',
    component:()=>import('@/components/menu/shop.vue')
  }
]

//需要查看用户权限来动态添加路由
export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  //默认可以访问的路由
  routes: defaultRoutes
})

существуетstroe.jsимпортировать в:

существуетstore.jsДобавьте код в:

Исправлятьmain.jsКод в (выделено):

тест в порядке:

Конечно, вы также можете аннотировать тест

Таким образом, этот маршрут возвращается бэкэндом, если у вас нет разрешения, вы вообще его не видите.

Хорошо, это дело здесь.

Исходный код кейса:Адрес источника

Эта глава закончилась


^_<