一、前言
要想統一處理所有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');