Vue项目使用拦截器和JWT验证 完整案例

tech2025-03-30  4

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达关注公众号后台回复pay或mall获取实战项目资料视频 点击此链接:一套SpringCloud电商项目,拿来即用(附详细教程和文档)!作者:YXi 来源:juejin.im/post/6844903959883218951)

几乎在所有的项目中都离不开拦截器和登录验证,这是必需的。如果你学会了这个 demo,那么几乎所有网站的登录验证,加载动画就都会了,所以背也要背会

所以本章以一个 demo 为例,来帮助大家理解拦截器和登录验证控制

文章后面有源码,可以下载下来运行一下

先来看看效果:

功能:

当你访问首页的时候,会有一个加载动画,就是拦截器的功劳,并且首页会有一个当前登录的用户名,默认是 wangcai,等你登录成功后,会替换成你自己登录的用户名

当你没有登录的时候,可以访问首页和登录页,但是访问不了个人中心 (Profile),当你访问个人中心,会给你自动跳转到登录页

当你在登录页进行登录,如果用户名输入错误的话,会弹出错误信息

当你输入正确的时候 (我设置了 Fan 为正确的用户名),点击登录,登录成功后,会自动给你跳转到首页

并且登录成功后,如果你再点击想进入登录页,是不行的,他会自动给你跳转到首页

登录成功后,就可以访问 个人中心页面

如果你超过 20 秒 不对页面进行操作 (我设置的是 20 秒,可以自行设置),那么 token 会自动失效,那么你就访问不了个人中心,你需要再次登录

如果你在 20 秒 之内,操作页面的话,那么 token 的值是不会失效的,所以是不需要再次登录的。也就是说,在 20 秒 之内,你每次进行路由跳转的时候,token 的值和时间就会自动重置,防止失效让你再次登录(总不能让你看着看着突然让你登录)

搜索公纵号:MarkerHub,关注回复[ vue ]获取前后端入门教程!

下面就让我们开始吧!!!(有关代码的解释说明已在代码中注释)

案例

使用拦截器并封装axios

新建一个 Vue 项目 (vue create demo)

删去不必要的文件和代码,经典化代码

安装需要的依赖:

package.json文件部分代码:

"dependencies": { "axios": "^0.19.0", "body-parser": "^1.19.0", "core-js": "^2.6.5", "express": "^4.17.1", "iview": "^4.0.0-rc.4", "jsonwebtoken": "^8.5.1", "vue": "^2.6.10", "vue-router": "^3.0.3", "vuex": "^3.0.1" },

在server.js文件中配置跨域,并书写测试接口:

let express = require('express') let bodyParser = require('body-parser') let app = express() // 配置跨域 app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "\*"); res.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT"), res.header("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization") if (req.method.toLowerCase() === "options") { return res.end(); } next(); }) // 配置bodyparser app.use(bodyParser.json()) app.get("/user", (req, res) => { //在请求数据时,要加一个动画,为了测试,所以让它时间长点,加了一个定时器 setTimeout(() => { res.json({ name: "wangcai" }) }, 500) }) app.listen(3000)

在router.js中配置路由:

routes: \[ { path: '/', name: 'home', component: Home }, { path: '/login', name: 'login', component: () => import('./views/Login.vue') }, { path: '/profile', name: 'profile', component: () => import('./views/Profile.vue') } \]

因为项目中需要用到样式什么的,这里我引入了iView,main.js代码:

import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' //引入iView import iView from 'iview' import 'iview/dist/styles/iview.css'; Vue.use(iView) Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app')

因为项目中要用到加载数据的动画,所以需要在store.js中的state中配置:

state: { //定义动画是否显示 isShowLoading:false, username:'wangcai' }, mutations: { //使动画显示 showLoading(state){ state.isShowLoading = true; }, //使动画隐藏 hideLoading(state){ state.isShowLoading = false; } },

在App.vue中配置跳转:

<template> <div> <div> <router-link to="/">Home</router-link> | <router-link to="/login">Login</router-link> | <router-link to="/profile">Profile</router-link> </div> <router-view/> </div> </template>

Home.vue代码:

<template> <div> <h1>首页面</h1> </div> </template>

Login.vue代码:

<template> <div> <i-input placeholder="请输入用户名..."></i-input> <i-button type="primary">登录</i-button> </div> </template>

Profile.vue代码:

<template> <div> <h1>个人中心</h1> </div> </template>

然后在libs文件夹下面新建一个ajaxRequest.js文件,用来封装我们自己的 axios 和 加载动画 等

搜索公纵号:MarkerHub,关注回复[ vue ]获取前后端入门教程!

import axios from 'axios' import store from '../store' //当第一次请求时,显示loading class AjaxRequest { //当new的时候,调用这个方法 constructor() { //请求的基础路径 this.baseURL = process.env.NODE\_ENV == "production" ? "/" : "http://localhost:3000" this.timeout = 3000 //超时时间 this.queue = {} //存放每一次的请求 } //定义一个方法,把options展开 merge(options) { return { ...options, baseURL: this.baseURL, timeout: this.timeout } } //封装一个拦截方法 setInterceptor(instance, url) { //请求拦截,每次请求时,都要加上一个loading效果 instance.interceptors.request.use((config) => { //每次请求时,都给他加一个Authorization头,在JWT验证时要用 config.headers.Authorization = 'xxx' //第一次请求时,显示loading动画 if (Object.keys(this.queue).length === 0) { store.commit('showLoading') } this.queue\[url\] = url; return config }) //响应拦截 instance.interceptors.response.use((res) => { //删除queue里面的链接,如果同一个按钮,你一秒之内点击无数次,但是他只处理第一次操作 delete this.queue\[url\] //隐藏loading动画 if (Object.keys(this.queue).length === 0) { store.commit('hideLoading') } //返回的结果 return res.data }) } request(options) { let instance = axios.create() //创建一个axios实例 this.setInterceptor(instance, options.url) //设置拦截 let config = this.merge(options) return instance(config) //axios执行后,返回promise } } export default new AjaxRequest;

然后在api文件夹下新建一个user.js文件用来放用户相关的调用接口的方法(当你想要调用接口的时候,直接调用里面的方法就好):

import axios from '../libs/ajaxRequset' // 用户相关的接口 export const getUser = ()=>{ return axios.request({ url:'/user', method:'get' }) }

修改Home.vue中的代码:

<template> <div> <h1>首页面</h1> <p>当前登录的用户名是{{$store.state.username}}</p> </div> </template> <script> //如果用export导出的话,要用这种形式,相当于解构赋值 import {getUser} from '../api/user' export default { name:'home', async mounted(){ let r = await getUser() console.log(r); } } </script>

修改App.vue中的代码(加动画效果):

<template> <div> <Spin size="large" fix v-if="$store.state.isShowLoading"> 加载中... </Spin> <div> <router-link to="/">Home</router-link> | <router-link to="/login">Login</router-link> | <router-link to="/profile">Profile</router-link> </div> <router-view/> </div> </template> <style> #app { font-family: "Avenir", Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } #nav { padding: 30px; } #nav a { font-weight: bold; color: #2c3e50; } #nav a.router-link-exact-active { color: #42b983; } </style>

运行效果:

使用 JWT

在server.js中新增代码,使用 JWT,并写好登录和验证的接口:

let jwt = require('jsonwebtoken') let secret = "xwc" //登录的接口 app.post('/login',(req,res)=>{ let {username} = req.body; if(username === 'Fan'){ //登录成功后返回一个token res.json({ code:0, username:'Fan', token:jwt.sign({username:'Fan'},secret,{ expiresIn:20 //表示token20秒过期 }) }) }else{ //登录失败 res.json({ code:1, data:'登录失败了' }) } }) //验证token的接口 app.get('/validate',(req,res)=>{ let token = req.headers.authorization; //我们会把token放到我们自己设置的http的头authorization中,在这里可以直接拿到 jwt.verify(token,secret,(err,decode)=>{ //验证token if(err){ return res.json({ code:1, data:'token失效了' }) }else{ // token合法 在这里,需要把token的时效延长, //总不能我们看着看着突然让我们重新登录,token过期的意思是,你在这之间不进行任何操作才会过期 res.json({ code:0, username:decode.username, token:jwt.sign({username:'Fan'},secret,{ //合法时,我们需要重新生成一个token,我们每次切换路由,都要重新生成一个token expiresIn:20 }) }) } }) })

接着在api文件夹下的user.js文件中添加登录和验证的方法:

import axios from '../libs/ajaxRequest' // 用户相关的接口 // 调获取用户信息的接口 向外暴露一个getUser方法 这个方法中调了接口 // 在组件中,就可以使用getUser,就相当于调用接口 export const getUser = ()=>{ return axios.request({ url:'/user', method:'get' }) } // 再向外暴露一个登录的方法,方法内部也是调接口 // 在登录组件中就可以调用Login方法,需要给方法传递一个用户名 export const login = (username)=>{ return axios.request({ url:'/login', method:'post', data:{ username } }) } //验证token方法 export const validate = ()=>{ return axios.request({ url:'/validate', method:'get' }) }

接着我们在lib文件夹下新建一个local.js文件,用来设置或者获取localStorage里的token:

//把获得到的token存到localStorage里 export const setLocal = (key,value)=>{ if(typeof value == 'object'){ //如果传过来的是对象,则转换成字符串 value = JSON.stringify(value) } localStorage.setItem(key,value) //存到localStorage里 } //获取localStorage里的token export const getLocal = (key)=>{ return localStorage.getItem(key) }

然后修改store.js中的代码:

import Vue from 'vue' import Vuex from 'vuex' import {login,validate} from './api/user' //必须用这种方式引入 import {setLocal} from './libs/local' //引入lib文件夹下的local.js文件中的setLocal方法(往localStorage里存放token) Vue.use(Vuex) export default new Vuex.Store({ state: { //定义动画是否显示 isShowLoading:false, username:'wangcai' }, mutations: { //使动画显示 showLoading(state){ state.isShowLoading = true; }, //使动画隐藏 hideLoading(state){ state.isShowLoading = false; }, //定义修改用户名的方法 setUser(state,username){ state.username = username } }, // actions存放接口的调用 dispatch actions里面放方法 actions: { //这里面所有的方法都是异步的 //登录方法 async toLogin({commit},username){ let r = await login(username) //调用user.js中的login方法,也就是调用登录接口 // console.log(r); if(r.code === 0){ //登录成功后会给你返回json数据,里面有code //登录成功了 commit('setUser',r.username) //修改用户名 setLocal('token',r.token) //把得到的token存到localStorage里 }else{ // console.log('............'); return Promise.reject(r.data); //如果失败,返回一个promise失败态 } }, //验证token方法 async validate({commit}){ let r = await validate(); //调用user.js中的validate方法,也就是调用验证接口 if(r.code === 0){ commit('setUser',r.username) setLocal('token',r.token) //我们说了,验证通过,或者每次切换路由时,都要重新生成token } return r.code === 0; //返回token是否失效,true或者false } } })

修改Login.vue中的代码:

<template> <div> <i-input v-model="username" placeholder="请输入用户名..."></i-input> <i-button type="primary" @click="login()">登录</i-button> </div> </template> <script> import {mapActions} from 'vuex' //使用vuex中的mapActions方法,不会的请参考我的文章vuex的使用方法 export default { data(){ return{ username:'' //定义一个用户名 } }, methods:{ ...mapActions(\['toLogin'\]), //获取store.js文件中的actions中的toLogin方法 login(){ // console.log(this\['toLogin'\](this.username)); //使用获取到的toLogin方法 this\['toLogin'\](this.username).then(data=>{ //因为toLogin返回的是一个Promise,所以可以.then this.$router.push('/') //登录成功,跳到首页面 },err=>{ this.$Message.error(err) }) } } } </script>

别忘了修改ajaxRequest.js文件,在请求拦截的时候,需要加个头,前面我们写死了,这里,要把 token 给他,然后每次路由跳转访问页面的时候,都会带上这个头,用来验证:

import {getLocal} from "../libs/local" //引入 //请求拦截,每次请求时,都要加上一个loading效果 instance.interceptors.request.use((config) => { //每次请求时,都给他加一个Authorization头,在JWT验证时要用 config.headers.Authorization = getLocal('token') //第一次请求时,显示loading动画 if (Object.keys(this.queue).length === 0) { store.commit('showLoading') } this.queue\[url\] = url; return config })

接着在router.js中设置路由:哪个页面需要登录后才能访问的话,给这个路由添加meta,假如我的 个人中心页面 需要登录后才能访问,那么我需要修改代码:

{ path: '/profile', name: 'profile', component: () => import('./views/Profile.vue'), meta:{ needLogin:true } }

最后修改main.js中的代码,当切换路由时,进行验证:

import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' //引入iView import iView from 'iview' import 'iview/dist/styles/iview.css'; Vue.use(iView) Vue.config.productionTip = false //每一次切换路由时,都执行这个导航守卫 router.beforeEach(async (to,from,next)=>{ let isLogin = await store.dispatch('validate') //判断是否登录了 // needLogin 表示哪些路由需要在登录条件下才能访问 console.log(to); let needLogin = to.matched.some(match=>match.meta.needLogin) if(needLogin){ //需要登录 if(isLogin){ //登录过了 next() }else{ //没有登录 next('/login') } }else{ //不需要登录 if(isLogin && to.path === '/login'){ //如果你访问login页面,则给你跳到首页面,因为不需要登录 next('/') }else{ next() } } }) new Vue({ router, store, render: h => h(App) }).$mount('#app')

有关需要注意的点,都加注释了,好好看注释就行

至此,整个案例就结束了

有热门推荐????

你的登录接口真的安全吗?反正我们公司的又被攻击了

为什么MySQL不推荐使用uuid或者雪花id作为主键?

Spring Validation最佳实践及其实现原理,参数校验没那么简单!

Spring Boot 2.3.3 正式发布!67 处 bug 修复!

万字详解,JDK8 的 Lambda、Stream 和日期的使用详解

干货分享:扫码关注下面的公众号后台回复“99”领取99套实战项目+资料

想充电就关注程序员闪充宝

点击阅读原文,获免费JVM+MySQL+设计模式+分布式+微服务完整面试资料

最新回复(0)