Json?Web?Token在前后端實(shí)踐思考分析
1、前言
啥也不說了,直接進(jìn)入正題,來學(xué)習(xí)一下Token在前端和后端的簡單應(yīng)用分析
Token是在客戶端頻繁向服務(wù)端請(qǐng)求數(shù)據(jù),服務(wù)端頻繁的去數(shù)據(jù)庫查詢用戶名和密碼進(jìn)行對(duì)比,判斷用戶名和密碼是否正確,并作出相應(yīng)提示,在這樣的背景下,Token便應(yīng)運(yùn)而生。
Token實(shí)際上就是在第一個(gè)登錄的時(shí)候通過用戶名和密碼,在服務(wù)端驗(yàn)證OK后生成的一串字符串,也可以說是驗(yàn)證通過后服務(wù)端為其簽發(fā)一個(gè)令牌,隨后前端在訪問服務(wù)端接口時(shí),客戶端就可以攜帶這個(gè)TOken令牌訪問服務(wù)器,服務(wù)端只需要驗(yàn)證令牌的有效性即可。
下面便是請(qǐng)求接口的一個(gè)大致過程
- 先登錄,獲取Token
- 調(diào)用業(yè)務(wù)接口,后端要先驗(yàn)證Token
- 驗(yàn)證OK,才繼續(xù)調(diào)用業(yè)務(wù)接口返回?cái)?shù)據(jù)
- 驗(yàn)證失敗,則返回給前端,比如Token過期,則重新跳轉(zhuǎn)到登錄

2、后端
登錄接口,通過用戶名和密碼,或者手機(jī)號(hào)驗(yàn)證碼的方式通過驗(yàn)證
public async Task<dynamic> Login([FromServices] IAuthService authService, [FromBody] FormLoginRequest loginModel)
{
return await authService.login(loginModel);
// authoService.login中的邏輯
// 判斷是否匹配,匹配成功
// 創(chuàng)建token并寫入redis,并設(shè)置超期時(shí)間
// 之前業(yè)務(wù)接口調(diào)用時(shí),直接從redis中獲取
// 如果有超期,返回給前端一個(gè)標(biāo)識(shí)
}
這里有一個(gè)創(chuàng)建Token的過程,我們來看一下token的組成

我找了一個(gè)公司正在開發(fā)項(xiàng)目中的token進(jìn)行解析查看。主要結(jié)構(gòu)如上圖所示。解密以后最重要的信息便是uid,或者說是用戶在后端中的唯一的用戶id,那么通過uid便可以查詢到相關(guān)的身份認(rèn)證信息。
截圖所示便是JSON Web Token的組成結(jié)構(gòu),從截圖左側(cè)仔細(xì)可以看到,中間有兩個(gè).將JWT分成了三個(gè)部分
- HEADER
alg屬性表示簽名的算法(algorithm),默認(rèn)是 HMAC SHA256(寫成 HS256)
typ屬性表示這個(gè)令牌(token)的類型(type)
- PAYLOAD 中間部分存放的就是實(shí)際要傳輸?shù)臄?shù)據(jù)
- Signature部分是對(duì)前面的兩部分的數(shù)據(jù)進(jìn)行簽名,防止數(shù)據(jù)篡改。
- 最終生成便是
首先明確一點(diǎn),是在后端生成的Token,后端會(huì)先定義一個(gè)秘鑰,這個(gè)秘鑰只有后端服務(wù)器才知道
不能泄露給用戶,然后使用Header中指定的簽名算法(默認(rèn)情況是HMAC SHA256),
算出簽名以后將Header、Payload、Signature三部分拼成一個(gè)字符串,每個(gè)部分用`.`分割開來,
就可以返給用戶了。
前端在登錄認(rèn)證通過獲得Token并保存到前端以后,再調(diào)用業(yè)務(wù)接口的時(shí)候每次便會(huì)攜帶Token
后端服務(wù)會(huì)通過全局注冊的環(huán)繞AOP,處理每次前端有請(qǐng)求到達(dá)后端的時(shí)候來對(duì)token校驗(yàn)
AllowAnonymousAttribute allowAnonymousAttribute = descriptor.MethodInfo.GetCustomAttribute<AllowAnonymousAttribute>(false);
// 判斷可不驗(yàn)證token的接口
if (allowAnonymousAttribute != null)
{
await next();
return;
}
//獲取請(qǐng)求頭中的Authorization
string token = context.HttpContext.Request.Headers["Authorization"];
// 相當(dāng)于對(duì)前端傳遞的token進(jìn)行轉(zhuǎn)換
string tokenKey = "sso." + Utils.MD5(token);
// redis獲取,看看是否有效,直接取出返回
string loginUserJson = await RedisHelper.GetAsync(tokenKey);
if (!loginUserJson.IsNullOrWhiteSpace()) {
RedisSSOVerifyResult resultInfo = JsonSerializer.Deserialize<RedisSSOVerifyResult>(loginUserJson);
if(resultInfo.ExpiresAt > DateTime.now()) {
loginUser = resultInfo.LoginUser;
}
else {
RedisHelper.RemoveAsync(tokenKey); // 無效了 從redis中移除
throw new ValidException("Token認(rèn)證過期,請(qǐng)重新登錄", -2); // 這里用-2跟前端做好約定
}
} else {
throw new ValidException("Token認(rèn)證過期,請(qǐng)重新登錄", -2); // 這里用-2跟前端做好約定
}
大致的一個(gè)token認(rèn)證過程是這樣的,實(shí)際項(xiàng)目中相對(duì)來說還是比較復(fù)雜的,這是我從公司項(xiàng)目中扣取出來的。還有很多代碼沒有列出來,要不然會(huì)顯得比較臃腫,而且主要邏輯不容易查看。
3、前端
通過登錄頁面,輸入登錄名和密碼,或者手機(jī)號(hào)和驗(yàn)證碼,獲取到token,現(xiàn)將token存儲(chǔ)到localStorage中,再通過token獲取其他業(yè)務(wù)接口的數(shù)據(jù)。 通??赡苁紫韧ㄟ^token獲取個(gè)人信息或者一些權(quán)限數(shù)據(jù)(這里只是提一下)。
const adminLogin = async () => {
// state.loading = true
const res = await loginByMobile({
mobile: state.loginForm.phone,
captchaValue: state.loginForm.verificationCode,
});
state.loading = false;
if (res?.code === 200) {
localStorage.setItem(
"token",
JSON.stringify({
...res.data,
account: state.loginForm.phone,
})
);
store.dispatch("fetchMenu");
}
};
我這里登錄完,直接通過token來獲取當(dāng)前登錄用戶的個(gè)人信息以及后臺(tái)勾選的菜單權(quán)限,后端分別通過兩個(gè)接口進(jìn)行的數(shù)據(jù)返回。
async fetchMenu({ commit }) {
try {
const information = await getMyInformation()
if (information?.code === 200) {
console.log(information, 'information')
commit("setMyInformation" , information.data)
const res = await getMyMenu()
if(res?.code === 200) {
commit("changeMenuList",res.data)
window.location.href = "/"
}
}
} catch (error) {
}
},
這里是axios針對(duì)每次的請(qǐng)求添加請(qǐng)求頭的Authorization
instance.interceptors.request.use(
(request) => {
const token = localStorage.token
? JSON.parse(localStorage.token)
: {};
request.headers = {
"Authorization": token.authorization || '',
"Content-Type": "application/x-www-form-urlencoded",
"Content-Type": "application/json",
};
return request;
},
(error) => Promise.reject(error)
);
這里是針對(duì)后端接口返回?cái)?shù)據(jù)的判斷處理,其中有一個(gè)-2的特殊判斷,這里是跟后端返回一起約定的code
instance.interceptors.response.use(
(response) => {
// token
if (response.data.code === -2) {
// token失效
ElMessage({
message: "身份認(rèn)證無效,請(qǐng)重新登錄",
type: "warning",
});
// localStorage.clear();
clear()
window.location.href = "/";
return false;
}
if (response.data.code !== 200) {
return Promise.reject(new Error(response.data.message));
}
/// ..... 其他的邏輯判斷
return response.data;
},
}
上面通過 code為-2 進(jìn)行判斷 ,然后清除掉緩存數(shù)據(jù),那么在vue-router路由中會(huì)進(jìn)行判斷處理
router.beforeEach((to, _from, next) => {
NProgress.start()
if (to.path === '/login' || to.path === '/init-password' || to.path === '/login-cellphone') {
next()
return false;
}
if (!localStorage.getItem('token')) {
next('/login')
return false
}
if (to.name) {
next()
return false
}
if (childrenPath.some((item) => to.path.includes(item))) {
next()
console.log('child');
return false
}
// 如果找不到路由跳轉(zhuǎn)到404
next("/404")
return false
})
總結(jié)
前端和后端大致的一個(gè)過程就在這里簡單說完了,梳理完了以后,發(fā)現(xiàn)自己更清楚了,其實(shí)還有很多的問題要去處理,比如
- 請(qǐng)求業(yè)務(wù)接口Token超期失效了該怎么辦? 可以通過每次調(diào)用業(yè)務(wù)接口的前,只要驗(yàn)證Token成功,就延遲Token的超期時(shí)間,但是這種方式每次都要去處理Token的時(shí)間,相對(duì)來說就比較麻煩,而且對(duì)服務(wù)器有一定的損耗。
- 那還有更好的辦法嗎? 當(dāng)然也是有的。比如通過雙Token進(jìn)行無痛刷新,就是當(dāng)一個(gè)token失效或者超期后,通過另外一個(gè)refresh_token來重新獲取token的處理,獲取成功后,再重新調(diào)用期間異常的業(yè)務(wù)接口
- 當(dāng)然肯定還有其他的方式吧,暫時(shí)能想到的就這么多了。
以上就是Json Web Token在前后端實(shí)踐思考分析的詳細(xì)內(nèi)容,更多關(guān)于Json Web Token前后端的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
uniapp中使用計(jì)時(shí)器setInterval的場景與方法
計(jì)時(shí)器在日常開發(fā)中經(jīng)常會(huì)遇到,下面這篇文章主要給大家介紹了關(guān)于uniapp中使用計(jì)時(shí)器setInterval的場景與方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
javascript中tostring()和valueof()的用法及兩者的區(qū)別
基本上,所有JS數(shù)據(jù)類型都擁有valueOf和toString這兩個(gè)方法,null除外。它們倆解決javascript值運(yùn)算與顯示的問題,本文給大家介紹javascript中tostring()和valueof()的用法及兩者的區(qū)別,對(duì)js tostring valueof相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2015-11-11
詳解實(shí)現(xiàn)一個(gè)通用的“劃詞高亮”在線筆記功能
這篇文章主要介紹了實(shí)現(xiàn)一個(gè)通用的“劃詞高亮”在線筆記文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
一文熟練掌握J(rèn)avaScript中Object.keys()與Object.values()的實(shí)用技巧
本文詳細(xì)介紹了JavaScript中Object.keys()和Object.values()方法的使用方法、功能及應(yīng)用場景,通過實(shí)例展示了如何使用這些方法進(jìn)行對(duì)象的遍歷、復(fù)制、比較和屬性計(jì)算等操作,需要的朋友可以參考下2025-01-01
一個(gè)js隨機(jī)顏色腳本(用于標(biāo)簽頁面,也可用于任何頁面)
一個(gè)js隨機(jī)顏色腳本(用于標(biāo)簽頁面,也可用于任何頁面)...2007-09-09
Echarts圖表分析巴西隊(duì)歷年戰(zhàn)績實(shí)例詳解
這篇文章主要為大家介紹了Echarts圖表分析巴西隊(duì)歷年戰(zhàn)績實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12

