Vue中的登录查看权限控制
- 1. 安装vuex使用存储功能
- 2. model/storage.js
- 3. store/index.js
- 4. utils/request.js和config/index.js
- 5. router/index.js
- 6. 具体应用
- 7. 问题:刷新页面以后store中的数据会丢失,重新返回登录页面
参考: 【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验证大致思路如下:
- 第一次登录的时候,前端调后端的登陆接口,发送电话号码(phone)和密码(password)
- 后端收到请求,验证phone和password,验证成功,就给前端返回一个token
- 前端拿到token,将token存储到localStorage和vuex中,并跳转路由首页index
- 前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
- 每次调后端接口,都要在请求头中加token
- 后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回400,请求头中没有token也返回400(参考【2】)
- 如果前端拿到状态码为400,就清除token信息并跳转到登录页面
2. model/storage.js
相当于一个方法,里面定义了一些对存储的信息进行的操作,不写这个方法直接写在store/index.js中也可以。
在这里实现了几个方法:
- 设置所储存的key和value
- 通过key获得value
- 移除key对应的value
- 移除所有暂存信息
- 获得所有暂存信息
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()
}
})