https://blog.csdn.net/qq_38128179/article/details/105262950
最近學習vue-cli3搭建后台管理項目,關於系統登錄攔截和獲取用戶權限控制這一塊是卡了挺久的一個難點,后台項目權限驗證與安全性是非常重要的,可以說是一個后台項目一開始就必須考慮和搭建的基礎核心功能。這篇文章寫一下前后端分離下的登錄解決方案,目前大多數都采用請求頭攜帶 Token 的形式。
一、整體思路
首次登錄時,后端服務器判斷用戶賬號密碼正確之后,根據用戶id、用戶名、定義好的秘鑰、過期時間生成 token ,返回給前端;
前端拿到后端返回的 token ,存儲在 localStroage 和 Vuex 里;
前端每次路由跳轉,判斷 localStroage 有無 token ,沒有則跳轉到登錄頁,有則請求獲取用戶信息,改變登錄狀態;
每次請求接口,在 Axios 請求頭里攜帶 token;
后端接口判斷請求頭有無 token,沒有或者 token 過期,返回401;
前端得到 401 狀態碼,重定向到登錄頁面,回到第一步。
二、封裝axios
登錄成功后,把后台返回的 Token 存在localStroage,檢查有無 Token ,每次請求在 Axios 請求頭上進行攜帶
如果狀態碼是401,則有可能是 Token 過期,需要跳轉到登錄頁進行登錄重新獲取Token。
axios封裝需根據后台返回的數據格式封裝,例如我司后台返回數據格式如下:
import axios from "axios";
import { Message } from "iview";
let router = import("@/router");
axios.defaults.baseURL = "/api";
axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
axios.defaults.headers["X-Requested-With"] = "XMLHttpRequest";
axios.defaults.headers["Cache-Control"] = "no-cache";
axios.defaults.headers["pragma"] = "no-cache";
let source = axios.CancelToken.source();
//請求添加token
axios.interceptors.request.use(request => {
request.headers["Authorization"] = window.localStorage.getItem('token') ? window.localStorage.getItem('token') : "";
return request;
});
//登錄過期(token失效)跳轉到登錄頁
axios.interceptors.response.use(response => {
let data = response.data;
if (
data.state && [401].includes(data.state.code)
) {
router.then(lib => {
if (lib.default.currentRoute.name === "login") return;
lib.default.push({
name: "login"
})
Message.warning(data.state.msg);
});
}
return response;
})
//返回值解構
axios.interceptors.response.use(response => {
let data = response.data;
let isJson = (response.headers["content-type"] || "").includes("json");
if (isJson) {
if (data.state && data.state.code === 200) {
return Promise.resolve({
data: data.data,
msg: data.state.msg,
code: data.state.code,
page: data.page
});
}
return Promise.reject(
data.state &&
data.state.msg ||
"網絡錯誤"
);
} else {
return data;
}
}, err => {
let isCancel = axios.isCancel(err);
if (isCancel) {
return new Promise(() => {});
}
return Promise.reject(
err.response.data &&
err.response.data.state &&
err.response.data.state.msg ||
"網絡錯誤"
);
})
//切換頁面取消請求
axios.interceptors.request.use(request => {
request.cancelToken = source.token;
return request;
});
router.then(lib => {
lib.default.beforeEach((to, from, next) => {
source.cancel()
source = axios.CancelToken.source();
next()
})
})
export function post(url, data, otherConfig) {
return axios.post(url, data, otherConfig);
}
export function get(url, data, otherConfig) {
return axios.get(url, {
params: data,
...otherConfig
});
}
三、路由攔截
首先在定義路由的時候就需要多添加一個自定義字段requireAuth,用於判斷用戶訪問該路由時是否需要登錄。例如:在用戶直接跳轉mainPage頁面的時候,需要判斷用戶是否登錄,那么我們在該路由下添加meta字段。
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes: [{
path: '/',
name: 'MainPage',
component: mainPage,
children: pages,
meta: {
requiresAuth: true // 訪問該路由時需要判斷是否登錄
}
},
{
path: '/login',
name: 'login',
component: Login
},
]
})
給mainPage首頁路由增加了 requiresAuth,所以我們需要在mainPage頁面里面使用路由鈎子beforeRouteEnter來攔截路由,
路由攔截兩個作用:
1.獲取用戶信息
由於每次刷新頁面store里面的信息會清空,因此我們可以在beforeRouteEnter增加獲取用戶信息接口,這樣每次清空store信息后我們再去調取接口重新獲取存入store
2.登陸攔截
根據當前路由的requireAuth字段判斷當前頁面是否需要登錄驗證,若需要登錄驗證,查看localStroage里有無Token ,有就繼續執行,如果沒有Token ,重定向到登錄頁。
// mainPage頁面
import LeftNav from "@/components/leftNav";
import { getLoginInfo, logout } from "@/api/getData";
import store from "@/store";
export default {
components: { LeftNav },
// 路由攔截
async beforeRouteEnter(to, from, next) {
// 因為刷新頁面每次都會清空strore里面的信息,因此每次刷新我們先調用獲取用戶信息接口獲取用戶信息存入vuex
let { data } = await getLoginInfo();
store.commit("setLoginInfo", data);
//判斷是否需要登錄驗證
let token = window.localStorage.getItem("token");
if (to.meta.requiresAuth) {
if (token) {
next();
} else {
// 調取退出接口,將頁面路由重定向到登錄頁進行登錄
logOut()
next({
path: "/login",
query: { redirect: to.fullPath }
});
}
} else {
next();
}
}
};
四、Vuex配置
上面我們調用用戶信息接口時用到了store進行存儲,因此需要配置
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
loginInfo: {},
},
mutations: {
setLoginInfo(state, data) {
state.loginInfo = data;
},
},
actions: {},
modules: {}
})
五、login頁面登錄
這里我寫了個登錄組件,下面是點擊登錄時的 handleSubmit 方法,登錄成功后我們需要把后端返回的token存入localStrage里頭,跳轉到首頁之前重定向過來的頁面。
async handleSubmit() {
// 先校驗表單輸入
let flag = await this.$refs.loginForm.validate();
if (!flag) return;
try {
this.loading = true;
// 調用登錄接口
let { msg } = await login({
userName: this.user,
password: this.password
});
this.$Message.success(msg);
window.localStorage.setItem('token', res.data.token) // 登錄成功后將后台返回的token存到localStorage
// 跳回指定路由
let redirectUrl = decodeURIComponent(this.$route.query.redirect || "/");
this.$router.push({ path: redirectUrl });
} catch (e) {
this.$Message.warning(e);
} finally {
this.loading = false;
}
}
}
整體流程跑完了,實現的主要功能就是:
訪問登錄注冊之外的路由,都需要登錄權限,比如首頁,判斷有無token,有則訪問成功,沒有則跳轉到登錄頁面;
成功登錄之后,保存后端返回的token,跳轉到之前重定向過來的頁面;
token 過期后,請求接口時,身份過期,跳轉到登錄頁,繼續第二步;
六、菜單權限
我們在首頁調用getLoginInfo接口返回了用戶信息、菜單列表和權限按鈕列表的JSON數據,存入Vuex里頭,這時我們就可以在左側導航組件里根據后台返回的菜單列表數據動態顯示左側導航菜單,從而實現了頁面權限
七、按鈕權限
【7.1】自定義auth指令
我們定義一個auth指令來顯示隱藏頁面元素,從而實現按鈕權限
// util/auth.js
import store from "@/store";
export default {
bind(el, binding, vnode) {
let auths = store.state.loginInfo.roleBtns; // 后台返回的按鈕權限列表
let hasAuth = auths.includes(binding.value);
if (hasAuth) {
delete el.style.display;
} else {
el.style.display = "none";
}
}
}
【7.2】全局注冊
【7.3】在頁面使用
如下所示,通過自定義指令v-auth綁定字段,當綁定的字段包含在后端返回的roleBtns列表里,那么該按鈕會顯示,否則隱藏
<div class="header-right">
<el-button v-auth="'editProduct'" type="primary" size="mini" @click="addProductRelation">新增
</el-button>
<el-button v-auth="'deleteProduct'" type="primary" size="mini" @click="deleteSelectProduct">刪除</el-button>
</div>
————————————————
版權聲明:本文為CSDN博主「@Demi」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_38128179/java/article/details/105262950