vue使用vuex管理token进行身份认证

928 阅读3分钟

流程:

1. 前端登录,输入用户名和密码到后端。 

2. 后端验证用户名和密码,若通过,生成一个token返回给前端。 

3. 前端拿到token使用vuex存储到localStorage管理,登录成功进入首页。 

4. 之后前端每一次权限操作如跳转路由,都需要判断是否存在token,若不存在,跳转至登录页。 

5. 前端之后的每一个对后端的请求都要在请求头上带上token,后端查看请求头是否有token,拿到token检查是否过期,返回对应状态给前端。 

6. 若token已过期,清除token信息,跳转至登录页。 

登录页 — Login.vue

<template>
	<div id="Login">
		<div id="login_wrap">
			<div id="title_wrap">
				<h1>欢迎登陆</h1>
				<p>福天科技后台管理系统</p>
			</div>
			<div id="form_wrap">
				<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="55px" class="demo-ruleForm">
					<el-form-item label="账号" prop="username">
						<el-input placeholder="请输入用户名" v-model="ruleForm.username" clearable></el-input>
					</el-form-item>
					<el-form-item label="密码" prop="password">
						<el-input placeholder="请输入密码" v-model="ruleForm.password" clearable show-password></el-input>
					</el-form-item>
					<el-button @click="submitForm('ruleForm')" id="loginBtn" type="primary">登录</el-button>
				</el-form>
			</div>
			
		</div>
	</div>
</template>

<script>
	import {mapState, mapMutations, mapActions, mapGetters} from 'vuex'
	
	export default {
		name: 'Login',
		data() {
			
			// 验证用户名
			const regUserName = /^[a-zA-Z0-9]+$/
			const checkUserName = (rule, value, callback) => {
			    if (!regUserName.test(value)) {
					return callback(new Error('只能包含a-zA-Z0-9的字符'))
			    } else {
					callback()
			    }
			}
			
			// 验证密码
			const regPassword = /^[\S]{6,12}$/
			const checkPassword = (rule, value, callback) => {
			    if (!regPassword.test(value)) {
					return callback(new Error('不能输入空格或者制表符'))
			    } else {
					callback()
			    }
			}
			
			return {
				ruleForm: {
					f: '',
					password: ''
				},
				
				// 表单验证规则
				rules: {
					username: [
						{ required: true, message: '请输入用户名', trigger: 'blur' },
						{ min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' },
						{ validator: checkUserName, trigger: 'blur' }
					],
					password: [
						{ required: true, message: '请输入密码', trigger: 'blur' },
						{ min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' },
						{ validator: checkPassword, trigger: 'blur' }
					]
				}
				
			}
		},
		
		computed: {
			
		},
		
		methods: {
			
			// 登录
		    submitForm(formName) {
				this.$refs[formName].validate((valid) => {
					if (valid) { // 表单验证成功
						this.$api.login(this.ruleForm).then(res=>{
							console.log(res)
							this.setToken(res.data.token)
							//判断路由是否带参,带参就去到重定向参数地址,否则就去首页
							if(this.$route.query.redirect){
							    this.$router.replace({path:this.$route.query.redirect})
							}else{
							    this.$router.replace({path:'/'})
							}
						})
					} else { // 表单验证失败
						return false
					}
		        })
		    },
			...mapMutations({
			    setToken: 'user/SET_TOKEN'
			})
		}
	}
</script>

<style scoped lang="stylus">
	#Login
		box-sizing border-box
		display flex
		justify-content center
		align-items center
		height 100%
		background-image: linear-gradient(24deg, #262a5e, #fa9046)
		color #FFFFFF
		min-height 500px
		#login_wrap
			display flex
			height 70%
			width 65%
			min-height 500px
			#title_wrap
				background-color rgba(121, 106, 238, 0.9)
				flex 1
				display flex
				flex-direction column
				justify-content center
				border-radius 8px 0 0 8px
				padding-left 40px
				padding-right 100px
				h1 
					font-size 2.5em
					font-weight 600
					padding-bottom 15px
			#form_wrap
				background-color #FFFFFF
				flex 1
				display flex
				flex-direction column
				justify-content center
				border-radius 0 8px 8px 0
				padding-left 40px
				padding-right 5vw
				#loginBtn
					padding-left 50px
					padding-right 50px
					margin-left 55px
				
</style>

路由守卫 — router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import store from '../store'
import storage from '../utils/storage'

Vue.use(Router)

const routes = [
  {
    path: '/',
    name: 'home',
    // redirect: '/login',
    // 某些页面规定必须登录后才能查看 ,可以在router中配置meta,将需要登录的requireAuth设为true,
    meta: {
      requireAuth: true
    },
	component: () => import('../pages/Home')
  },

  {
    path: '/login',
    component: () => import('../pages/login/Login')
  }
]

const router = new Router({ routes: routes })

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

export default router

封装axios 添加请求拦截器 在每次请求之前进行的操作

import axios from 'axios'
import store from '../store'

const request = axios.create({
	baseURL: 'http://127.0.0.1:3000',
	timeout: 5000
})

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

//respone拦截器
request.interceptors.response.use(
    response => {
        // 在status正确的情况下,status不正确则返回对应的错误信息(后台自定义为0是正确,并且将错误信息写在message),正确则返回响应
        return response.data.status === 0 ? response : Promise.reject(response.data.message);
    },
    error => { 
        // 在status不正确的情况下,判别status状态码给出对应响应
        if (error.response) {
            console.log("在respone拦截器显示错误:", error.response)
            switch (error.response.status) {
                case 401:
                    //可能是token过期,清除token
                    store.commit('user/DEL_TOKEN')

                    router.replace({ //跳转到登录页面
                        path: '/login',
                         // 将跳转的路由path作为参数,登录成功后跳转到该路由
                        query: { redirect: router.currentRoute.fullPath }
                    })
            }
        }
        return Promise.reject(error.response.data);
    }
)

export default request

封装操作localstorage本地存储的方法

const storage = {
	
	// 存储
    set(key, value) {
        localStorage.setItem(key, JSON.stringify(value))
    },
	
	// 获取
    get(key) {
        return JSON.parse(localStorage.getItem(key))
    },
	
	// 根据索引获取key
    getForIndex(index) {
        return localStorage.key(index)
    },
	
	// 获取所有key
    getKeys(){
        let items = this.getAll()
        let keys = []
        for (let index=0;index<items.length;index++){
            keys.push(items[index].key)
        }
        return keys
    },
	
	// 获取localStorage长度
    getLength() {
        return localStorage.length
    },
	
	
    getSupport() {
        return (typeof (Storage) !== "undefined") ? true : false
    },
	
	// 移除一个数据
    remove(key) {
        localStorage.removeItem(key)
    },
	
	// 清空localStorage
    removeAll() {
        localStorage.clear()
    },
	
	// 获取localStorage所有数据
    getAll() {
        let len = localStorage.length  // 获取长度
        let arr = new Array() // 定义数据集
        for (var i = 0; i < len; i++) {
            // 获取key 索引从0开始
            var getKey = localStorage.key(i)
            // 获取key对应的值
            var getVal = localStorage.getItem(getKey)
            // 放进数组
            arr[i] = {
                'key': getKey,
                'val': getVal,
            }
        }
        return arr
    }
}

export default storage

vuex模块的user.js模块

import storage from '../../utils/storage'

// 按需引入 types.js 文件
import { SET_TOKEN, DEL_TOKEN } from '../type.js'


const state = {
	token: ''
}
 
const getters = {
	get_token(state){
		return state.token || storage.get('token') || null
	}
}
 
const mutations = {
	// 修改token,并将token存入localStorage
	[SET_TOKEN] (state, token) {
		console.log(token)
	    state.token = token
	    storage.set('token', token)
	},
	// 删除token
	[DEL_TOKEN] (state) {
	    state.token = ""
	    storage.remove('token')
	}
}
 
const actions = {
	
}
 
export default {
    namespaced: true,
    state,
    getters,
    mutations,
    actions
}