雙token無感刷新機制的實現(xiàn)方法示例
前文我們實現(xiàn)了登錄鑒權(quán),接下來我們對其進行優(yōu)化,實現(xiàn)雙token無感刷新
什么是無感刷新?
舉個例子:
早上入園(登錄成功)
- 前臺給你:當天門票(
access_token)+ 年卡(refresh_token)
- 前臺給你:當天門票(
玩過山車(請求數(shù)據(jù))
下午門票過期(
access_token失效)- 檢票員攔住你:"票過期了!"(接口返回401錯誤)
自動續(xù)票(無感刷新)
你默默掏出年卡(
refresh_token)續(xù)票亭給你:新的當天門票(新
access_token)
繼續(xù)游玩(請求重發(fā))
用新門票重新玩過山車(自動重試之前失敗的請求)
你完全沒中斷游玩(用戶無感知)
簡單來說,就是將之前登錄鑒權(quán)使用的一個token換成兩個token,access_token(短token)和refresh_token(長token),access_token主要用于驗證用戶是否登錄,而refresh_token主要作用是更新access_token和refresh_token,從而延長用戶的使用時間,不需頻繁登錄。
如何實現(xiàn)雙token無感刷新?
1.將后端返回給登錄請求的數(shù)據(jù)中的單token改為access_token和refresh_token
router.prefix('/user')//路由前綴,表示所有路由都以/user開頭
router.post('/login',async (ctx)=>{
const {username,password} = ctx.request.body
try{
const res = await userLogin(username,password)
if(res.length){
let data={
id:res[0].id,
username:res[0].username,
nikeName:res[0].nikeName,
create_time:res[0].create_time,
}
const access_token = sign(data,'1h')//生成短token令牌
const refresh_token=sign(data,'7d')//生成長token令牌
ctx.body={
code:'1',//邏輯成功
msg:'登錄成功',
data,
access_token:access_token,//登錄成功后,后端返回一個短token給前端
refresh_token:refresh_token//登錄成功后,后端返回一個長token給前端
}
}else{
ctx.body={
code:'0',
msg:'賬號或密碼錯誤',
}
}
}catch(error){
ctx.body={
code:'-1',
msg:'服務(wù)器異常',
data:error
}
}
})2.將access_token和refresh_token一起進行瀏覽器本地保存
用戶登錄成功后,才能接收到,并進行保存
const onFinish = values => {
axios.post('/user/login',values).then(res=>{
toast.success('登錄成功')
navigate('/noteClass')
localStorage.setItem('access_token',res.access_token)//登錄成功后,將令牌(token)存儲到瀏覽器本地
localStorage.setItem('refresh_token',res.refresh_token)//登錄成功后,將令牌(token)存儲到瀏覽器本地
}).catch(err=>{
console.log(err)
})
}3. axios請求攔截器添加到請求頭上的數(shù)據(jù)改access_token
axios.interceptors.request.use(request => {
const access_token = localStorage.getItem('access_token')//從瀏覽器本地獲取token
if (access_token) {//如果令牌(token)存在,將令牌(token)放到請求頭中
request.headers.Authorization = access_token//將令牌(token)放到請求頭中
}
return request//返回請求數(shù)據(jù)(放行)
})4. 處理access_token過期邏輯
當首頁發(fā)送text接口請求時,后端驗證發(fā)現(xiàn)access_token過期了,則返回401狀態(tài)碼,axios響應(yīng)攔截器攔截到401狀態(tài)碼,則將瀏覽器中的refresh_token放入請求體,并發(fā)送refresh接口請求,后端收到refresh_token后對其進行驗證,如果refresh_token過期,直接返回416狀態(tài)碼,告訴前端跳轉(zhuǎn)登錄頁面,如果refresh_token驗證通過,則生成新的access_token和refresh_token,返回給前端,前端接收后,攜帶新的access_token重新發(fā)送text接口請求。
- 后端驗證access_token過期,返回401狀態(tài)碼
function verify(){
//ctx最終為index.js中的use方法中的ctx參數(shù)
//next為內(nèi)置的,表示執(zhí)行了app.use中的回調(diào)函數(shù)之后要運行一個next,這樣后面的app.use()才會生效
return async (ctx,next)=>{
//1.獲取請求頭中的token
const token = ctx.request.headers.authorization
//2.驗證token是否存在
if(token){
//3.驗證token是否正確
try{
const decoded = jwt.verify(token,'666')//解密token
//判斷decoded是否存在,因為能解密成功,說明token沒有過期,且有id屬性
if(decoded.id){
await next()//放行
}
}catch(error){//如果短token過期
ctx.status=401
ctx.body={
code:'0',
msg:'登錄過期,請重新登錄',
}
}
}else{//無token
ctx.status=402
ctx.body={
code:'0',
msg:'請先登錄'
}
}
}
}- axios響應(yīng)攔截器攔截401,并進行access_token和refresh_token刷新
- 將refresh_token作為請求體,發(fā)送refresh接口請求
(res) => {
//后端返回的短token過期的狀態(tài)碼為401
if (res.status === 401) {
const originalRequest = res.config//獲取原始請求,將沒有成功的原始請求記錄下來
//重新請求新的短token和長token
const refresh_token = localStorage.getItem('refresh_token')//從瀏覽器本地獲取token
if(refresh_token){
axios.post('/user/refresh',{refresh_token}).then(res=>{
if(res.code==='1'){
//將新的短token放到瀏覽器本地
localStorage.setItem('access_token',res.access_token)
//將新的長token放到瀏覽器本地
localStorage.setItem('refresh_token',res.refresh_token)
//將原始請求的headers中的Authorization屬性設(shè)置為新的短token
originalRequest.headers.Authorization = res.access_token
//重新發(fā)送原始請求
return axios(originalRequest)
}
})
}
}
//如果長短token都過期了,后端返回的狀態(tài)碼為416
if(res.status===416){
toast.error(res.response.data.msg)
setTimeout(() => {
window.location.href = '/login'//使用原生js跳轉(zhuǎn)方式,跳轉(zhuǎn)到登錄頁
}, 2000)
}
if(res.status===402){
toast.error(res.response.data.msg)
setTimeout(() => {
window.location.href = '/login'//使用原生js跳轉(zhuǎn)方式,跳轉(zhuǎn)到登錄頁
}, 2000)
}- 驗證refresh_token是否過期
//驗證refresh_token
function refreshVerify(token){
try{
const decoded = jwt.verify(token,'666')//解密token
if(decoded.id){
return decoded
}
}catch(error){
return false
}
}- 驗證成功,刷新access_token和refresh_token
- 失敗則返回416狀態(tài)碼
router.post('/refresh',async (ctx)=>{
const {refresh_token} =ctx.request.body
//校驗refresh_token是否有效
const vaild=refreshVerify(refresh_token)
if(vaild.id){//如果長token解密成功
//創(chuàng)建新的長短token
const data={
id:vaild.id,
username:vaild.username,
nikeName:vaild.nikeName,
create_time:vaild.create_time,
}
const access_token = sign(data,'1h')//生成短token令牌
const refresh_token=sign(data,'7d')//生成長token令牌
ctx.body={
code:'1',
msg:'刷新成功',
access_token:access_token,
refresh_token:refresh_token,
}
}else{//長token過期
ctx.status=416,
ctx.body={
code:'0',
msg:'登錄過期,請重新登錄',
}
}
})- 最后重新發(fā)送text接口請求(代碼在第一小點后面)


總結(jié)
到此這篇關(guān)于雙token無感刷新機制實現(xiàn)的文章就介紹到這了,更多相關(guān)雙token無感刷新機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
設(shè)置jsf的選擇框h:selectOneMenu為不可編輯狀態(tài)的方法
本文為大家詳細介紹下如何設(shè)置jsf的選擇框h:selectOneMenu為不可編輯狀態(tài),具體實現(xiàn)代碼如下,希望對大家有所幫助2014-01-01
詳解js中常規(guī)日期格式處理、月歷渲染和倒計時函數(shù)
大家在日常開發(fā)的時候經(jīng)常要用到日期格式的處理,下面這篇文章主要給大家介紹了js中常規(guī)日期格式處理、月歷渲染及倒計時函數(shù),有需要的朋友可以參考借鑒,下面來一起看看吧。2016-12-12
js實現(xiàn)感應(yīng)鼠標圖片透明度變化的方法
這篇文章主要介紹了js實現(xiàn)感應(yīng)鼠標圖片透明度變化的方法,涉及javascript鼠標操作及css修改技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-02-02

