在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面
4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、每次调后端接口,都要在请求头中加token
6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面
.
.
.
.
实操代码:
1.login页(账号密码登录完成后,将后台返回的token储存到本地);最后附上了登录页完整代码
handleSubmit(e) {var that = this;this.userName = this.userName.trim();this.password = this.password.trim();if (this.userName === "" && this.password === "") {that.$message.warning("账号和密码无内容");return;}if (this.userName === "") {that.$message.warning("账号无内容");return;}if (this.password === "") {that.$message.warning("密码无内容");return;}var username = document.getElementById("username").value;var password = document.getElementById("password").value;e.preventDefault();this.form.validateFields((err, values) => {//这一步将用户名储存在vuex ||||||||||目前域账号和用户名都是写一样了||||||||||||||||||||||||||||||||||||||||||||||||axios.post(this.$store.state.windowCONTENT + "sysConfig/getUserRole", {loginUser: this.userName,}).then((res) => {if (res.data.success == 0) {//有权限this.$store.state.loginUser = this.userName;this.$store.state.userName = this.userName;this.$store.state.roleCode = res.data.data.roleCode;//存入tokenlocalStorage.setItem("token",JSON.stringify(res.data.data.token));// console.log(res.data.data,this.$store.state.roleCode);if (!err) {this.$router.push("/layout");this.$message.success("登陆成功");}} else {//无权限this.$message.error(res.data.data);}});});},
2.marn.js(配置请求拦截器,每次请求携带token,后端进行验证;配置响应拦截器,根据后端验证返回的结果,判断token是否过期)
import "babel-polyfill";
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import { Button, message } from 'ant-design-vue';
import Antd from "ant-design-vue";
import "ant-design-vue/dist/antd.css";
import zh_CN from "ant-design-vue/lib/locale-provider/zh_CN";
import moment from "moment";
import "moment/locale/zh-cn";
import "./assets/iconfont/iconfont";
import axios from "axios";Vue.component(Button.name, Button);Vue.config.productionTip = false;Vue.use(Antd);
Vue.prototype.$message = message;// 设置axios全局默认的BASE-URL, 只要设置了全局的默认base_url,以后的请求会自动拼接上base_url
//axios.defaults.baseURL = 'http://localhost:8888/api/private/v1/'// 配置axios的请求拦截器-(每次在请求头上携带后台分配的token-后台判断token是否有效等问题)
axios.interceptors.request.use(function(config) {// 在发送请求之前做些什么// console.log('请求到了哟', config.headers.Authorization)// 统一的给config设置 tokenconfig.headers.Authorization = JSON.parse(localStorage.getItem("token"));config.headers['Token'] = JSON.parse(localStorage.getItem("token"));return config;},function(error) {// 对请求错误做些什么return Promise.reject(error);}
);//响应拦截器 与后端定义状态是100时候是错误 跳转到登录界面
axios.interceptors.response.use(function (response) {// 对响应数据做点什么console.log(response)//当返回信息为未登录或者登录失效的时候重定向为登录页面if (response.data.status == 100 || response.data.message == '用户未登录或登录超时,请登录!') {router.push({path: "/login",querry: { redirect: router.currentRoute.fullPath }//从哪个页面跳转})message.warning(response.data.message);}return response;
}, function (error) {// 对响应错误做点什么return Promise.reject(error)
})moment.locale("zh-cn");
new Vue({zh_CN,router,watch: {// 监听路由变化"$route.path": function(newVal, oldVal) {console.log(`new_path = ${newVal}, old_path = ${oldVal}`);},},store,render: (h) => h(App),
}).$mount("#app");
3.router.js(配置导航守卫,有token或者是去登录页就通过,否则定向到登录页)
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Layout from '../components/Layout.vue'
import Login from '../components/Login.vue'Vue.use(VueRouter)const routes = [{path: '/',redirect: 'login'},{path: '/login',name: 'Login',component: Login},{path: '/layout',name: 'Layout',component: Layout},{path: '/about',name: 'About',// route level code-splitting// this generates a separate chunk (about.[hash].js) for this route// which is lazy-loaded when the route is visited.component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')}
]const router = new VueRouter({routes
})// to 到哪去
// from 从哪来
// next 是否放行 next() 放行 next('/login') 拦截到登录
// 如果准备去登录, 不需要拦截
// 如果已经登录过了, 有token, 不需要拦截
// 如果不是去登陆, 且没有 token, 拦截到登录页
router.beforeEach((to, from, next) => {const token = JSON.parse(localStorage.getItem('token'));console.log(token);// console.log(to)if (to.path === '/login' || token) {next()} else {next('/login')}
})export default router
4.退出:清除本地储存的token
//退出登录signOut() {console.log("点击了退出", this.$route.query.sessionId);localStorage.clear("token");this.$route.push('/')//将vuex的数据初始化和清除默认的数据},
.
.
.
.
.
.
完整登录页
<template><div class="login"><div class="logo"><img :src="logoUrl" alt /></div><div class="welcome">WELCOME</div><div class="platform">欢迎来到XX平台</div><div class="box"><a-formid="components-form-demo-normal-login":form="form"class="login-form"@submit.prevent="handleSubmit"><a-form-item><div class="l-bor">登录</div></a-form-item><a-form-item><div class="account">账号</div></a-form-item><a-form-item><a-input v-model="userName" allowClear class="user-name" placeholder="请输入"><!-- v-decorator="['userName',{ rules: [{ required: true, message: '请输入账号名!' }] },]"--><a-icon slot="prefix" type="user" style="color: rgba(0,0,0,.25)" /></a-input></a-form-item><a-form-item><div class="p-box">密码</div></a-form-item><a-form-item><!-- <a-inputv-decorator="['password',{ rules: [{ required: true, message: 'Please input your Password!' }] },]"type="password"placeholder="Password"><a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)" /></a-input>--><a-input-passwordv-model="password"allowClearclass="password"type="password"placeholder="请输入"><!-- v-decorator="['password',{ rules: [{ required: true, message: '请输入密码!' }] },]"--><a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)" /></a-input-password></a-form-item><a-form-item><!-- <a-checkboxv-decorator="['remember',{valuePropName: 'checked',initialValue: true,},]">Remember me</a-checkbox>--><!-- <a class="login-form-forgot" href>Forgot password</a> --><a-button:style="{opacity: (btnFlag ? .5 : 1)}"type="primary"html-type="submit"class="login-form-button"><spanstyle="width:58px;height:24px;font-size:17px;font-family:PingFang-SC-Light,PingFang-SC;font-weight:300;color:rgba(255,255,255,1);line-height:24px;text-shadow:0px 2px 3px rgba(62,32,201,0.1);">登录</span></a-button><!-- Or --><!-- <a href>register now!</a> --></a-form-item></a-form></div><div class="bottom"></div></div>
</template><script>
import axios from "axios";
export default {data() {return {logoUrl: require("../assets/logo.svg"),userName: "",password: "",btnFlag: true,flagColor: {opacity: 0.5,},};},beforeCreate() {this.form = this.$form.createForm(this, { name: "normal_login" });},created() {},methods: {handleSubmit(e) {var that = this;this.userName = this.userName.trim();this.password = this.password.trim();if (this.userName === "" && this.password === "") {that.$message.warning("账号和密码无内容");return;}if (this.userName === "") {that.$message.warning("账号无内容");return;}if (this.password === "") {that.$message.warning("密码无内容");return;}// jucenter.submit({name:this.userName,pwd:this.password});e.preventDefault();this.form.validateFields((err, values) => {//这一步将用户名储存在vuex ||||||||||目前域账号和用户名都是写一样了||||||||||||||||||||||||||||||||||||||||||||||||console.log(this.userName, this.password);axios.post(this.$store.state.windowCONTENT + "sysConfig/getUserRole",{loginUser: this.userName}).then(res => {if (res.data.success == 0) {//有权限this.$store.state.loginUser = this.loginUser;this.$store.state.userName = res.data.data.userName;this.$store.state.roleCode = res.data.data.roleCode;//存入tokenlocalStorage.setItem("token",JSON.stringify(res.data.data.token));if (!err) {this.$router.push("/layout");console.log(111);this.$message.success('登陆成功')}} else {//无权限this.$message.error(res.data.data)}})});},},watch: {userName: function (v1, v2) {var that = this;if (v1 && this.password) {this.btnFlag = false;console.log(this.btnFlag);} else {this.btnFlag = true;console.log(this.btnFlag);}},password: function (v1, v2) {var that = this;if (v1 && this.userName) {this.btnFlag = false;console.log(this.btnFlag);} else {this.btnFlag = true;console.log(this.btnFlag);}},},
};
</script>
<style lang="less">
.login {position: relative;text-align: left;height: 100%;background: linear-gradient(154deg,rgba(119, 182, 244, 1) 0%,rgba(66, 106, 229, 1) 100%);// background-color: pink!important;// overflow: hidden;.logo {position: absolute;top: 21px;left: 35px;}.welcome {position: absolute;bottom: 60%;left: 10%;width: 416px;height: 100px;font-size: 72px;font-family: PingFang-SC-Regular, PingFang-SC;font-weight: 400;color: rgba(255, 255, 255, 1);line-height: 100px;letter-spacing: 6px;}.platform {position: absolute;bottom: 53%;left: 10%;width: 313px;height: 33px;font-size: 24px;font-family: PingFang-SC-Light, PingFang-SC;font-weight: 300;color: rgba(255, 255, 255, 1);line-height: 33px;letter-spacing: 2px;}.box {position: absolute;bottom: 10%;right: 0;// margin: 0 auto;// margin-bottom: 151px; //定位高度-----↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓margin-right: 21%;width: 370px;height: 500px;border-radius: 10px;background: rgba(252, 251, 255, 1);padding: 0px 53px;//去除默认下边距.ant-form-item {margin-bottom: 0;}//输入框.ant-input-affix-wrapper .ant-input:not(:last-child) {height: 36px;font-size: 14px;font-family: PingFang-SC-Regular, PingFang-SC;font-weight: 400;color: rgba(53, 58, 64, 1);// background-color: pink;}//登录按钮#components-form-demo-normal-login .login-form-button {width: 264px;height: 44px;background: linear-gradient(90deg,rgba(60, 163, 247, 1) 0%,rgba(28, 106, 235, 1) 100%);box-shadow: 0px 2px 3px 0px rgba(62, 32, 201, 0.1);border-radius: 6px;}.l-bor {margin-left: -53px;margin-top: 50px;line-height: 25px;border-left: 6px solid rgba(28, 106, 235, 1);padding-left: 47px;font-size: 18px;font-family: PingFang-SC-Medium, PingFang-SC;font-weight: 500;color: rgba(53, 58, 64, 1);letter-spacing: 1px;}// .account{// margin-top: 58px;// width:27px;// height:17px;// font-size:12px;// font-family:PingFang-SC-Light,PingFang-SC;// font-weight:300;// color:rgba(53,58,64,1);// line-height:17px;// letter-spacing:1px;// }.ant-form-item:nth-child(2) {position: absolute;top: 133px;.account {width: 27px;height: 17px;font-size: 12px;font-family: PingFang-SC-Light, PingFang-SC;font-weight: 300;color: rgba(53, 58, 64, 1);line-height: 17px;letter-spacing: 1px;}}.ant-form-item:nth-child(3) {position: absolute;top: 160px;width: 264px;}.ant-form-item:nth-child(4) {position: absolute;top: 230px;width: 264px;.p-box {width: 27px;height: 17px;font-size: 12px;font-family: PingFang-SC-Light, PingFang-SC;font-weight: 300;color: rgba(53, 58, 64, 1);line-height: 17px;letter-spacing: 1px;}}.ant-form-item:nth-child(5) {position: absolute;top: 257px;width: 264px;}.ant-form-item:nth-child(6) {position: absolute;top: 376px;width: 264px;}}.bottom {position: absolute;bottom: 0;width: 100%;// height: 151px; //固定高度-----↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑height: 10%; //固定高度-----↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑background: linear-gradient(360deg,rgba(75, 118, 232, 0) 0%,rgba(57, 98, 224, 0.41) 100%);}
}
#components-form-demo-normal-login .login-form {max-width: 300px;
}
#components-form-demo-normal-login .login-form-forgot {float: right;
}
#components-form-demo-normal-login .login-form-button {width: 100%;
}
</style>