一、前言
要想统一处理所有http请求和响应,就得用上 axios 的拦截器。通过配置http response inteceptor
,当后端接口返回401 Unauthorized(未授权)
,让用户重新登录。
二、具体实现
当前端使用localStorage存储登陆信息,但是这个时候,后端一般在登陆的成功的时候,使用JWT(json web token)会生成token,这个token包含了失效时间和秘钥,以及用户的信息。比如:
1、登陆时候后端生成失效时间的JWT
const jwt = require("jsonwebtoken");
const token = jwt.sign(userToken, secret, {expiresIn: '7d'});
这个userToken可以是,比如是登陆进来的user的id和name。
const userToken = {
name: name,
id: res[0]["id"]
};
secret是自己定义的salt,确保这个jwt生成的字符串的随机性和不易破解性。比如项目英文名称
const secret = 'happy-chat-sec'
2、前端的http请求的封装
http请求都是需要需要经过axios的,通过axios的create的方法可以添加http的前缀和超时时间。比如这个实例:
const service = axios.create({
baseURL: '/',
// baseURL: '/',
timeout: 10000
})
通过interceptors.request.use方法对所有的http请求,进行封装,比如对于已经登陆的,localstorage里有这个字段HappyChatUserToken,说明需要在http报文头部加上Authorization这个字段用来校验前端是否登陆,后端会从http报文头部中取到这个值,并进行校验,是否是正确的。
service.interceptors.request.use(config => {
const token = localStorage.getItem('HappyChatUserToken');
if (token) {
/*Bearer是JWT的认证头部信息*/
config.headers.common['Authorization'] = 'Bearer ' + token;
}
return config;
},error => {
return Promise.reject(error);
});
3、前端的http返回的拦截
前端是本地缓存,但是后端才是最根本来控制登陆的,如果失效时间到了,jwt的token就会失效,但是前端还是拿着这个失效的token去请求后端接口,那肯定是拿不到数据的,这时候后端返回401,未授权。
此时后端代码遇到失效或者错误的JWT的返回
/*处理验证是否登陆*/
const jwt = require("jsonwebtoken");
const secret = require("../config").secret;
module.exports = async (ctx, next) => {
/*同步 校验token*/
const auth = ctx.get("Authorization");
const token = auth.split(' ')[1];
try {
const userToken = jwt.verify(token, secret)
ctx.user_id = userToken.id;
ctx.name = userToken.name;
await next()
} catch (err){
ctx.throw(401, err)
}
}
这时候前端对返回的http请求做拦截,并跳转到登陆页面
service.interceptors.response.use(
response => {
let data = response.data;
if (!data.data) {
// 登陆成功的回调地址
return data;
} else {
return data;
}
},
error => {
if(error.response) {
switch (error.response.status){
case 401:
/*返回401,清空token信息,关闭socketio,并跳转到登陆页*/
let userInfo = JSON.parse(localStorage.getItem("HappyChatUserInfo"));
socketWeb.emit('logout', userInfo.user_id)
localStorage.removeItem("HappyChatUserToken");
localStorage.removeItem("HappyChatUserInfo");
setTimeout(function() {
router.push({
path: "/login",
query: {redirect: router.currentRoute.fullPath}
});
}, 500);
}
}
return Promise.reject(error.response) // 返回接口返回的错误信息
}
);
跳转到登陆,同时使用setimeout定时器,作为延迟跳转,使用router.push({path:'/login',query: {redirect: router.currentRoute.fullPath}}),这里使用query的redirect的方式,用来解决:登录失效后跳转到去登录页面。登录后打开的是你最后登录的页面。
这时候,登陆成功跳转页面时不能写死写成首页。
let redirect = decodeURIComponent(this.$route.query.redirect || '/robot');
this.$router.push({ path: redirect });
上面代码是在登陆成功之后,先去$route.query.redirect中找是否有值,没有值,那就正常跳转到/robot路由上。
注意:decodeURIComponent函数编码的 URI 进行解码
三、总结
使用router.push({path:'/login',query: {redirect: router.currentRoute.fullPath}})用来登录失效后跳转到去登录页面。登录后打开的是你最后登录的页面。修改登陆页面的跳转let redirect = decodeURIComponent(this.$route.query.redirect || '/robot');