流程:
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',
meta: {
requireAuth: true
},
component: () => import('../pages/Home')
},
{
path: '/login',
component: () => import('../pages/login/Login')
}
]
const router = new Router({ routes: routes })
if (storage.get('token')) {
store.commit('user/SET_TOKEN', storage.get('token'))
}
router.beforeEach((to, from, next) => {
if (to.matched.some(r => r.meta.requireAuth)) {
if (store.state.user.token) {
next()
} else {
next({
path: "/login",
query: { redirect: to.fullPath }
})
}
} else {
next()
}
})
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
})
request.interceptors.request.use(
config => {
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);
}
);
request.interceptors.response.use(
response => {
return response.data.status === 0 ? response : Promise.reject(response.data.message);
},
error => {
if (error.response) {
console.log("在respone拦截器显示错误:", error.response)
switch (error.response.status) {
case 401:
store.commit('user/DEL_TOKEN')
router.replace({
path: '/login',
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))
},
getForIndex(index) {
return localStorage.key(index)
},
getKeys(){
let items = this.getAll()
let keys = []
for (let index=0;index<items.length;index++){
keys.push(items[index].key)
}
return keys
},
getLength() {
return localStorage.length
},
getSupport() {
return (typeof (Storage) !== "undefined") ? true : false
},
remove(key) {
localStorage.removeItem(key)
},
removeAll() {
localStorage.clear()
},
getAll() {
let len = localStorage.length
let arr = new Array()
for (var i = 0; i < len; i++) {
var getKey = localStorage.key(i)
var getVal = localStorage.getItem(getKey)
arr[i] = {
'key': getKey,
'val': getVal,
}
}
return arr
}
}
export default storage
vuex模块的user.js模块
import storage from '../../utils/storage'
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 = {
[SET_TOKEN] (state, token) {
console.log(token)
state.token = token
storage.set('token', token)
},
[DEL_TOKEN] (state) {
state.token = ""
storage.remove('token')
}
}
const actions = {
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}