• 管理系统权限篇


    目录

    前言

    cooike

    登录篇

    登录

    router.beforeEach 登录拦截 

    axios拦截器 token过期出处理

    权限篇

    router.js

    store/permission.js


    前言

    • 登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个token,拿到token之后(我会将这个token存贮到cookie中,保证刷新页面后能记住用户登录状态),前端会根据token再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息)。
    • 权限验证:通过token获取用户对应的 role,动态根据用户的 role 算出其对应有权限的路由,通过 router.addRoutes 动态挂载这些路由。

    cooike

    Cookie是由服务器端生成的,发送给User-Agent(一般是浏览器),(服务器告诉浏览器设置一下Cookie),浏览器会将cookie以key/value 的形式保存在某个目录下的文本文件内,下一次请求同一网站时就发送该Cookie服务器(前提是浏览器设置Cookie)

    Cookie的特点

    •  http是无状态协议 状态都是由Cookie来控制的
    • 有生命周期
    • 满足同源策略
    • 内存大小收到限制(一般是4K左右)

    生命周期:
    我们可以通过设置cookie的Expires的值来设置一条Cookie信息的失效时间 默认是当浏览器关闭的时候失效 我们可以利用new Date() 的 setTime和getTime来设置失效时间

    满足同源策略:
    不同源的情况下,Cookie一样是无法传递的。但是我们会发现我们登录了百度账号之后,再邓丽百度知道或百度文库,账号都是登录状态,但是这几个网页之间的主机名是不同的。
    js-cookie 

    js-cookie是什么?
    js-cookie是一个简单的,轻量级的处理cookies的js API,用来处理cookie相关的插件

    js-cookie的使用方法

    npm install --save js-cookie

    import Cookies from 'js-cookie'

     js-cookie的添加 获取 删除

    添加cookie

    1. // 创建一个名称为name,对应值为value的cookie,由于没有设置失效时间,默认失效时间为该网站关闭时
    2. Cookies.set(name, value)
    3. // 创建一个有效时间为7天的cookie
    4. Cookies.set(name, value, { expires: 7 })
    5. // 创建一个带有路径的cookie
    6. Cookies.set(name, value, { path: '' })
    7. // 创建一个value为对象的cookie
    8. const obj = { name: 'ryan' }
    9. Cookies.set('user', obj)

     获取cookie

    1. // 获取指定名称的cookie
    2. Cookies.get(name) // value
    3. // 获取value为对象的cookie
    4. const obj = { name: 'ryan' }
    5. Cookies.set('user', obj)
    6. JSON.parse(Cookies.get('user'))
    7. // 获取所有cookie
    8. Cookies.get()

     删除cookie

    1. // 删除指定名称的cookie
    2. Cookies.remove(name) // value
    3. // 删除带有路径的cookie
    4. Cookies.set(name, value, { path: '' })
    5. Cookies.remove(name, { path: '' })

     cooike操作的封装

    1. import Cookies from 'js-cookie'
    2. const TokenKey = 'Admin-Token'
    3. export function getToken() {
    4. return Cookies.get(TokenKey)
    5. }
    6. export function setToken(token) {
    7. return Cookies.set(TokenKey, token)
    8. }
    9. export function removeToken() {
    10. return Cookies.remove(TokenKey)
    11. }

    登录篇

    登录成功后,服务端会返回一个 token(该token的是一个能唯一标示用户身份的一个key),之后我们将token存储在本地cookie之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不用再去登录页面重新登录了。

    tips

    ps:为了保证安全性,后台所有token有效期(Expires/Max-Age)都是Session,就是当浏览器关闭了就丢失了。重新打开游览器都需要重新登录验证,后端也会在每周固定一个时间点重新刷新token,让后台用户全部重新登录一次,确保后台用户不会因为电脑遗失或者其它原因被人随意使用账号。

    登录

    click事件触发登录操作:

    1. this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
    2. this.$router.push({ path: '/' }); //登录成功之后重定向到首页
    3. }).catch(err => {
    4. this.$message.error(err); //登录失败提示错误
    5. });

    action:

    1. LoginByUsername({ commit }, userInfo) {
    2. const username = userInfo.username.trim()
    3. return new Promise((resolve, reject) => {
    4. loginByUsername(username, userInfo.password).then(response => {
    5. const data = response.data
    6. Cookies.set('Token', response.data.token) //登录成功后将token存储在cookie之中
    7. commit('SET_TOKEN', data.token)
    8. resolve()
    9. }).catch(error => {
    10. reject(error)
    11. });
    12. });
    13. }

    router.beforeEach 登录拦截 

    1. import router from './router'
    2. import store from './store'
    3. import { Message } from 'element-ui'
    4. import NProgress from 'nprogress' // 进度条
    5. import 'nprogress/nprogress.css' // 进度条样式
    6. import { getToken } from '@/utils/auth' // 获取身份信息
    7. import getPageTitle from '@/utils/get-page-title'
    8. NProgress.configure({ showSpinner: false }) // NProgress Configuration
    9. const whiteList = ['/login', '/auth-redirect'] // 无重定向白名单
    10. // 路由前置守卫
    11. router.beforeEach(async (to, from, next) => {
    12. // 启动进度条
    13. NProgress.start()
    14. //设置页面标题
    15. document.title = getPageTitle(to.meta.title)
    16. //确定用户是否已登录
    17. const hasToken = getToken()
    18. if (hasToken) {
    19. if (to.path === '/login') {
    20. //如果已登录,请重定向到主页
    21. next({ path: '/' })
    22. NProgress.done()
    23. } else {
    24. // 确定用户是否已通过getInfo获得其权限角色
    25. const hasRoles = store.getters.roles && store.getters.roles.length > 0
    26. if (hasRoles) {
    27. next()
    28. } else {
    29. try {
    30. //获取用户信息
    31. const { role } = await store.dispatch('user/getInfo')
    32. //基于角色生成可访问的路线图
    33. const accessRoutes = await store.dispatch('permission/generateRoutes', role)
    34. //动态添加可访问路由
    35. router.addRoutes(accessRoutes)
    36. // 以确保addRoutes是完整的
    37. next({ ...to, replace: true })
    38. } catch (error) {
    39. //删除令牌并转到登录页面重新登录
    40. await store.dispatch('user/resetToken')
    41. Message.error(error || 'Has Error')
    42. next(`/login?redirect=${to.path}`)
    43. NProgress.done()
    44. }
    45. }
    46. }
    47. } else {
    48. /*它没有令牌*/
    49. if (whiteList.indexOf(to.path) !== -1) {
    50. //在免费登录白名单中,直接进入
    51. next()
    52. } else {
    53. //其他没有访问权限的页面将重定向到登录页面。
    54. next(`/login?redirect=${to.path}`)
    55. NProgress.done()
    56. }
    57. }
    58. })
    59. router.afterEach(() => {
    60. //完成进度条
    61. NProgress.done()
    62. })

     用户信息

    没有把用户信息存储到本地 ,页面会先从 cookie 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有token,就会把这个 token 返给后端去拉取user_info,保证用户信息是最新的。(vuex刷新会丢失数据 写到路由拦截器里 每次去判断有没有用户信息)

    1. // get user info
    2. getInfo({ commit, state }) {
    3. // 获取用户信息及权限并保存
    4. return new Promise((resolve, reject) => {
    5. getInfo(state.token).then(response => {
    6. const { data } = response
    7. console.log(response);
    8. if (!data) {
    9. reject('Verification failed, please Login again.')
    10. }
    11. const { role, name, avatar, introduction } = data
    12. // roles must be a non-empty array
    13. if (!role || role.length <= 0) {
    14. reject('getInfo: roles must be a non-null array!')
    15. }
    16. commit('SET_ROLES', data.role)
    17. commit('SET_NAME', name)
    18. commit('SET_AVATAR', avatar)
    19. commit('SET_INTRODUCTION', introduction)
    20. resolve(data)
    21. }).catch(error => {
    22. reject(error)
    23. })
    24. })
    25. },

    tips

    假设我把用户权限和用户名也存在了本地,但我这时候用另一台电脑登录修改了自己的用户名,之后再用这台存有之前用户信息的电脑登录,它默认会去读取本地 cookie 中的名字,并不会去拉去新的用户信息。

    页面会先从 cookie 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有token,就会把这个 token 返给后端去拉取user_info,保证用户信息是最新的。 

    axios拦截器 token过期出处理

    我们通过request拦截器在每个请求头里面塞入token,好让后端对请求进行权限验证。并创建一个respone拦截器,当服务端返回特殊的状态码,我们统一做处理,如没权限或者token失效等操作。或者来做一些统一的异常处理,一劳永逸。 而且因为我们的 api 是根据 env 环境变量动态切换的,如果以后线上出现了bug,我们只需配置一下 @/config/dev.env.js 再重启一下服务,就能在本地模拟线上的环境了

    环境变量

    设置webpack配置文件 运行时可以检测环境(webpack对外暴露了 process可以拿到 env为环境变量。设置的值必须VUE_APP开头)

    • .env.development 开发
    • .env.production 生产
    • .env.staging 测试
    • process.env读取环境变量
    • 设置文件时值必须以VUE_APP开头
    1. //开发
    2. # just a flag
    3. ENV = 'development'
    4. # base api
    5. VUE_APP_BASE_API = '/dev-api'
    6. //生产
    7. # just a flag
    8. ENV = 'production'
    9. # base api
    10. VUE_APP_BASE_API = '/prod-api'
    11. //测试
    12. NODE_ENV = production
    13. # just a flag
    14. ENV = 'staging'
    15. # base api
    16. VUE_APP_BASE_API = '/stage-api'

     axios 封装

    1. import axios from 'axios'
    2. import { MessageBox, Message } from 'element-ui'
    3. import store from '@/store'
    4. import { getToken } from '@/utils/auth'
    5. // create an axios instance
    6. const service = axios.create({
    7. baseURL: process.env.VUE_APP_BASE_API,//基础路径+请求地址
    8. // withCredentials: true, // 跨域请求时发送Cookie
    9. timeout: 5000 // request timeout
    10. })
    11. // request interceptor
    12. service.interceptors.request.use(
    13. config => {
    14. // do something before request is sent
    15. if (store.getters.token) {
    16. // 设置token请求头
    17. config.headers['X-Token'] = getToken()
    18. }
    19. return config
    20. },
    21. error => {
    22. // do something with request error
    23. console.log(error) // for debug
    24. return Promise.reject(error)
    25. }
    26. )
    27. //响应拦截器
    28. service.interceptors.response.use(
    29. response => {
    30. const res = response.data
    31. // 如果自定义代码不是20000,则判断为错误。
    32. // if (res.code !== 20000) {
    33. // Message({
    34. // message: res.message || 'Error',
    35. // type: 'error',
    36. // duration: 5 * 1000
    37. // })
    38. // // 50008:非法令牌;50012:其他客户端登录;50014:令牌已过期;
    39. // if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
    40. // //为了重新登录
    41. // MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
    42. // confirmButtonText: 'Re-Login',
    43. // cancelButtonText: 'Cancel',
    44. // type: 'warning'
    45. // }).then(() => {
    46. // // 删除token
    47. // store.dispatch('user/resetToken').then(() => {
    48. // location.reload()
    49. // })
    50. // })
    51. // }
    52. // return Promise.reject(new Error(res.message || 'Error'))
    53. // } else {
    54. return res
    55. // }
    56. },
    57. error => {
    58. console.log('err' + error) // for debug
    59. Message({
    60. message: error.message,
    61. type: 'error',
    62. duration: 5 * 1000
    63. })
    64. return Promise.reject(error)
    65. }
    66. )
    67. export default service

    权限篇

    前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoutes动态挂载路由。

    • 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。
    • 当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
    • 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
    • 使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件。

    tips

    ddRouter 是 router 实例的一个方法, 可以 动态添加更多的路由规则

    这个 api ,可以让你的 router 配置,不用在初始化router实例的时候就写进去

    可以在 vue 实例化 以后,动态的添加更多的路由

    router.js

    1. // router.js
    2. import Vue from 'vue';
    3. import Router from 'vue-router';
    4. import Login from '../views/login/';
    5. const dashboard = resolve => require(['../views/dashboard/index'], resolve);
    6. //使用了vue-routerd的[Lazy Loading Routes
    7. ](https://router.vuejs.org/en/advanced/lazy-loading.html)
    8. //所有权限通用路由表
    9. //如首页和登录页和一些不用权限的公用页面
    10. export const constantRouterMap = [
    11. { path: '/login', component: Login },
    12. {
    13. path: '/',
    14. component: Layout,
    15. redirect: '/dashboard',
    16. name: '首页',
    17. children: [{ path: 'dashboard', component: dashboard }]
    18. },
    19. ]
    20. //实例化vue的时候只挂载constantRouter
    21. export default new Router({
    22. routes: constantRouterMap
    23. });
    24. //异步挂载的路由
    25. //动态需要根据权限加载的路由表
    26. export const asyncRouterMap = [
    27. {
    28. path: '/permission',
    29. component: Layout,
    30. name: '权限测试',
    31. meta: { role: ['admin','super_editor'] }, //页面需要的权限
    32. children: [
    33. {
    34. path: 'index',
    35. component: Permission,
    36. name: '权限测试页',
    37. meta: { role: ['admin','super_editor'] } //页面需要的权限
    38. }]
    39. },
    40. { path: '*', redirect: '/404', hidden: true }
    41. ];

    通过meta标签来标示改页面能访问的权限有哪些。如meta: { role: ['admin','super_editor'] }表示该页面只有admin和超级编辑才能有资格进入。里有一个需要非常注意的地方就是 404 页面一定要最后加载

    store/permission.js

    通过用户的权限和之前在router.js里面asyncRouterMap的每一个页面所需要的权限做匹配,最后返回一个该用户能够访问路由有哪些。

    1. // store/permission.js
    2. import { asyncRouterMap, constantRouterMap } from 'src/router';
    3. function hasPermission(roles, route) {
    4. if (route.meta && route.meta.role) {
    5. return roles.some(role => route.meta.role.indexOf(role) >= 0)
    6. } else {
    7. return true
    8. }
    9. }
    10. const permission = {
    11. state: {
    12. routers: constantRouterMap,
    13. addRouters: []
    14. },
    15. mutations: {
    16. SET_ROUTERS: (state, routers) => {
    17. state.addRouters = routers;
    18. state.routers = constantRouterMap.concat(routers);
    19. }
    20. },
    21. //动态路由
    22. actions: {
    23. GenerateRoutes({ commit }, data) {
    24. return new Promise(resolve => {
    25. let accessedRoutes
    26. console.log(role);
    27. if (role.includes('admin')) {
    28. var arr = []
    29. asyncRoutes.forEach((item) => {
    30. if (item.hasOwnProperty('meta')) {
    31. if (item.meta.hasOwnProperty('roles')) {
    32. if (item.meta.roles.includes('admin')) {
    33. arr.push(item)
    34. }
    35. }
    36. }
    37. })
    38. accessedRoutes = arr || []
    39. // accessedRoutes = asyncRoutes || []
    40. } else {
    41. accessedRoutes = filterAsyncRoutes(asyncRoutes, role)
    42. }
    43. commit('SET_ROUTES', accessedRoutes)
    44. resolve(accessedRoutes)
    45. })
    46. }
    47. }
    48. };
    49. export default permission;

    转载借鉴 vue-element-admin

    作者:花裤衩
    链接:https://juejin.cn/post/6844903478880370701

  • 相关阅读:
    centos7安装mysql8
    改变金融贷款市场营销方式 ---- 运营商大数据精准获客
    C# 定时器定时不准确问题
    C++继承同名成员的处理方式
    微信小程序日期增加时间完成订单失效倒计时(有效果图)
    迷你世界之建筑生成球体
    vue+electron 修改默认安装目录
    【C语言】结构体内存对齐机制详解
    客户收入企稳、亏损收窄,有赞在盛夏“回春”?
    暑期算法打卡----第二天
  • 原文地址:https://blog.csdn.net/qq_63358859/article/details/126377751