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ì)通過全局注冊(cè)的環(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)文章
echarts環(huán)形圖內(nèi)部圓、外部圓形及陰影設(shè)置方法
近期要做圖表,我選擇了ECharts做可視化圖表,圖表的樣式有陰影,這篇文章主要給大家介紹了關(guān)于echarts環(huán)形圖內(nèi)部圓、外部圓形及陰影設(shè)置的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11js判斷鼠標(biāo)左、中、右鍵哪個(gè)被點(diǎn)擊的方法
這篇文章主要介紹了js判斷鼠標(biāo)左、中、右鍵哪個(gè)被點(diǎn)擊的方法,主要通過event.button事件來判斷鼠標(biāo)點(diǎn)擊的類型,需要的朋友可以參考下2015-01-01相關(guān)JavaScript在覽器中實(shí)現(xiàn)可視化的四種方式
這篇文章主要介紹了相關(guān)JavaScript在覽器中實(shí)現(xiàn)可視化的四種方式,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-09-09基于javascript實(shí)現(xiàn)圖片預(yù)加載
這篇文章主要介紹了javascript圖片預(yù)加載的方法,實(shí)例分析了javascript實(shí)現(xiàn)圖片預(yù)加載的思路,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-01-01純js實(shí)現(xiàn)瀑布流展現(xiàn)照片(自動(dòng)適應(yīng)窗口大小)
用瀑布流來展現(xiàn)照片再好不過了,我的思路大概是一張一張的圖片插入,當(dāng)這一行的圖片保持長寬比例不變并且高度低于250時(shí)就完成一個(gè)了循環(huán),即這一行插入進(jìn)去了2013-04-04用Webpack構(gòu)建Vue項(xiàng)目的實(shí)踐
這篇文章主要介紹了用Webpack構(gòu)建Vue項(xiàng)目的實(shí)踐,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11