【Vue】登录查看权限控制(token)

参考:
【1】vue项目将token存在(vuex)store和localstorage中
【2】关于后端的权限控制(文章第三部分)

基本根据上面的参考修改。
主要添加的有这几部分:
目录

1. 安装vuex使用存储功能

【引用】

  • 了解(session,cookie)token
    Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。
    token 是在服务端产生的一串字符串,以作客户端进行请求的一个令牌。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌(除非设置了有效期)。

  • token 优点
    Token 完全由应用管理,所以它可以避开同源策略
    Token 可以避免 CSRF 攻击
    Token 可以是无状态的,可以在多个服务间共享
    减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

cnpm install vuex --save

在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:

  1. 第一次登录的时候,前端调后端的登陆接口,发送电话号码(phone)和密码(password)
  2. 后端收到请求,验证phone和password,验证成功,就给前端返回一个token
  3. 前端拿到token,将token存储到localStorage和vuex中,并跳转路由首页index
  4. 前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
  5. 每次调后端接口,都要在请求头中加token
  6. 后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回400,请求头中没有token也返回400(参考【2】)
  7. 如果前端拿到状态码为400,就清除token信息并跳转到登录页面

2. model/storage.js

相当于一个方法,里面定义了一些对存储的信息进行的操作,不写这个方法直接写在store/index.js中也可以。

在这里实现了几个方法:

  1. 设置所储存的key和value
  2. 通过key获得value
  3. 移除key对应的value
  4. 移除所有暂存信息
  5. 获得所有暂存信息
var storage = {
    
    
  set (key, value) {
    
    
    localStorage.setItem(key, JSON.stringify(value))
  },
  get (key) {
    
    
    return JSON.parse(localStorage.getItem(key))
  },
  remove (key) {
    
    
    localStorage.removeItem(key)
  },
  removeAll () {
    
    
    localStorage.clear()
  },
  getAll () {
    
    
    let len = localStorage.length
    // eslint-disable-next-line no-array-constructor
    let arr = []
    for (var i = 0; i < len; i++) {
    
    
      var getKey = localStorage.key(i)
      var getVal = localStorage.getItem(getKey)
      arr[i].push({
    
    
        'key': getKey,
        'val': getVal
      })
    }
    return arr
  }
}

export default storage

3. store/index.js

相当于具体的过程。
mutations中写的方法可以通过store.commit(方法名, 具体数值)来进行调用,比如要调用set_token(state, token)_this.$store.commit('set_token', token)

import Vue from 'vue'
import Vuex from 'vuex'
import storage from '../model/storage'

Vue.use(Vuex)
// 用Vuex.Store对象用来记录token
// 存储需要的信息
const store = new Vuex.Store({
    
    
  state: {
    
    
    token: '',
    info: {
    
    
      phone: '',
      job: '',
      id: ''
    }
  },

  // 计算属性
  mutations: {
    
    
    // 修改token,并将token存入localStorage
    set_token (state, token) {
    
    
      state.token = token
      storage.set('token', token)
      console.log('store、localstorage保存token成功!')
    },
    del_token (state) {
    
    
      state.token = ''
      storage.remove('token')
    },
    // 可选
    setUserInfo (state, userName) {
    
    
      state.userName = userName
    }
  },
  actions: {
    
    
    // removeToken: (context) => {
    
    
    // context.commit('set_token')
    // }
  }
})
export default store

4. utils/request.js和config/index.js

request.js相当于重写了一个request方法,在使用axios方法时,直接用重写的request即可。同时定义了拦截器的检查,如果后台返回的status是400的话(自己在后台定义具体状态码),即未登录/token失效。

import axios from 'axios'
import store from '@/store'
import router from '@/router'

// create an axios instance
const service = axios.create({
    
    
  baseURL: '/api', // url = base url + request url
  timeout: 5000 // request timeout
})

// 添加请求拦截器,若token存在则在请求头中加token,不存在也继续请求
service.interceptors.request.use(
  config => {
    
    
    // 每次发送请求之前检测都vuex存有token,那么都要放在请求头发送给服务器,没有则不带token
    // Authorization是必须的
    if (store.state.token) {
    
    
      config.headers.Authorization = store.getters.get_token
    }
    return config
  },
  error => {
    
    
    console.log('在request拦截器显示错误:', error.response)
    return Promise.reject(error)
  }
)

// respone拦截器
service.interceptors.response.use(
  response => {
    
    
    // 在status正确的情况下,code不正确则返回对应的错误信息(后台自定义为200是正确,并且将错误信息写在message),正确则返回响应
    return response.data.status === 200 ? response : Promise.reject(response.data.message)
  },
  error => {
    
    
    // 在status不正确的情况下,判别status状态码给出对应响应
    if (error.response) {
    
    
      console.log('在respone拦截器显示错误:', error.response)
      switch (error.response.status) {
    
    
        case 401:
          store.commit('del_token')
          router.replace({
    
    
            // 跳转到登录页面
            path: '/login',
            // 将跳转的路由path作为参数,登录成功后跳转到该路由
            query: {
    
     redirect: router.currentRoute.fullPath }
          })
          break
        case 400:
          store.commit('del_token')
          alert('请先登录')
          router.replace({
    
    
            // 跳转到登录页面
            path: '/login',
            // 将跳转的路由path作为参数,登录成功后跳转到该路由,方便登陆成功后再跳转到刚刚的页面
            query: {
    
     redirect: router.currentRoute.fullPath }
          })
          break
      }
    }
    return Promise.reject(error.response.data)
  }
)

export default service

把这两部分放在一起讲是因为,config/index.js定义了baseURL跳转路由的域名,使得能够跨域访问,并方便调用,即调用"/api"就是调用config中定义的target,仅修改proxyTable即可:

	proxyTable: {
    
    
      '/api': {
    
    
        target: 'http://localhost:8090',
        changeOrigin: true,
        pathRewrite: {
    
    
          '^/api': ''
        }
      }
    },

5. router/index.js

检查每个页面跳转时token是否失效。如果失效的话重新登陆。

import Vue from 'vue'
import Router from 'vue-router'
import Login from '../pages/login.vue'
import Index from '../pages/index.vue'
import store from '../store'
Vue.use(Router)

const routes = [
  {
    
    
    path: '/',
    name: 'Login',
    component: Login
  },
  {
    
    
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    
    
    path: '/index',
    name: 'Index',
    component: Index,
    meta: {
    
    
      requireAuth: true
    }
  }
]
const router = new Router({
    
     routes: routes })

router.beforeEach((to, from, next) => {
    
    
  // 判断要去的路由有没有requiresAuth
  // to.matched.some(r => r.meta.requireAuth) or to.meta.requiresAuth
  if (to.matched.some(r => r.meta.requireAuth)) {
    
    
    if (store.state.token) {
    
    
      // 有token,进行request请求,后台还会验证token
      next()
    } else {
    
    
      next({
    
    
        path: '/login',
        // 将刚刚要去的路由path(却无权限)作为参数,方便登录成功后直接跳转到该路由,这要进一步在登陆页面判断
        query: {
    
     redirect: to.fullPath }
      })
    }
  } else {
    
    
    next()
  }
})

export default router

6. 具体应用

首先在main.js中设置全局store:

import Vue from 'vue'
import App from './App'
import 'ant-design-vue/dist/antd.css'
import router from './router'
import Antd from 'ant-design-vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import store from './store'

Vue.use(VueAxios, axios)

Vue.config.productionTip = false
Vue.use(Antd)

/* eslint-disable no-new */
new Vue({
    
    
  el: '#app',
  router,
  store,
  components: {
    
     App },
  template: '<App/>'
})

把this重新定义为_this的原因:

  • 在进行values获取时,括号里的this不是总的this,所以获取不到$route和$store.
import request from '../utils/request.js'
export default {
    
    
  name: 'LoginForm',
  data () {
    
    
    return {
    
    
      form: this.$form.createForm(this, {
    
     name: 'login_form' })
    }
  },
  methods: {
    
    
    login (e) {
    
    
      e.preventDefault()
      let _this = this
      this.form.validateFields((err, values) => {
    
    
        if (!err) {
    
    
          request({
    
    
            url: '/login',
            method: 'post',
            params: {
    
    
              phone: values.phone,
              password: values.password
            }
          })
            .then(function (response) {
    
    
              console.log(response)
              if (response.status === 200) {
    
    
                const token = response.data.token
                _this.$store.commit('set_token', token)
                console.log(response.data)
                _this.$router.push('index')
              } else {
    
    
                // alert(response.data.data.message)
              }
            }).catch(function (error) {
    
    
              console.log(error)
            })
        }
      })
    }
  }
}

7. 问题:刷新页面以后store中的数据会丢失,重新返回登录页面

解决方案:使用sessionStorage存储token数据
需要修改app.vue

<script>
export default {
    
    
  name: 'App',
  data () {
    
    
    return {
    
    
      imgUrl: require('./assets/icon_logo.png')
    }
  },
  created () {
    
    
    if (sessionStorage.getItem('store')) {
    
    
      let state = JSON.parse(sessionStorage.getItem('store'))
      this.$store.commit('set_token', state.token)
      this.$store.commit('setUserInfo', state.info)
      sessionStorage.setItem('store', '')
    }
    window.onbeforeunload = this.beforeunloadFn
  },
  methods: {
    
    
    beforeunloadFn (e) {
    
    
      // 这个事件只有在鼠标真正和浏览器有了交互,再刷新或者关闭时才会触发, 浏览器事件会弹框确认用户是否要离开页面
      e = e || window.event
      if (e) {
    
    
        sessionStorage.setItem('store', JSON.stringify(this.$store.state))
      }
    }
  }
}
</script>

由于在跳转路由时就需要验证是否有token,所以router/index.js中也需要要修改对于有无token的验证:

实际只增加了对sessionStorage中储存的token的验证

router.beforeEach((to, from, next) => {
    
    
  // 判断要去的路由有没有requiresAuth
  // to.matched.some(r => r.meta.requireAuth) or to.meta.requiresAuth
  if (to.matched.some(r => r.meta.requireAuth)) {
    
    
    if (store.state.token || JSON.parse(sessionStorage.getItem('store')).token) {
    
    
      // 有token,进行request请求,后台还会验证token
      console.log('有token')
      next()
    } else {
    
    
      next({
    
    
        path: '/login',
        // 将刚刚要去的路由path(却无权限)作为参数,方便登录成功后直接跳转到该路由,这要进一步在登陆页面判断
        query: {
    
     redirect: to.fullPath }
      })
    }
  } else {
    
    
    next()
  }
})

猜你喜欢

转载自blog.csdn.net/Kandy0125/article/details/121365444
今日推荐