無(wú)感知刷新Token示例簡(jiǎn)析
引言
在前后端分離的應(yīng)用中,使用Token進(jìn)行認(rèn)證是一種較為常見(jiàn)的方式。但是,由于Token的有效期限制,需要不斷刷新Token,否則會(huì)導(dǎo)致用戶認(rèn)證失敗。為了解決這個(gè)問(wèn)題,可以實(shí)現(xiàn)無(wú)感知刷新Token的功能,本文將介紹如何實(shí)現(xiàn)無(wú)感知刷新Token。
Token認(rèn)證的原理
在Web應(yīng)用中,常見(jiàn)的Token認(rèn)證方式有基于Cookie和基于Token的認(rèn)證?;贑ookie的認(rèn)證方式是將認(rèn)證信息保存在Cookie中,每次請(qǐng)求時(shí)將Cookie發(fā)送給服務(wù)器進(jìn)行認(rèn)證;而基于Token的認(rèn)證方式是將認(rèn)證信息保存在Token中,每次請(qǐng)求時(shí)將Token發(fā)送給服務(wù)器進(jìn)行認(rèn)證。
在基于Token的認(rèn)證方式中,客戶端將認(rèn)證信息保存在Token中,而不是保存在Cookie中。在認(rèn)證成功后,服務(wù)器將生成一個(gè)Access Token和一個(gè)Refresh Token,并將它們返回給客戶端。Access Token用于訪問(wèn)受保護(hù)的API,Refresh Token用于獲取新的Access Token。
什么是無(wú)感知刷新Token
無(wú)感知刷新Token是指,在Token過(guò)期之前,系統(tǒng)自動(dòng)使用Refresh Token獲取新的Access Token,從而實(shí)現(xiàn)Token的無(wú)感知刷新,用戶可以無(wú)縫繼續(xù)使用應(yīng)用。
在實(shí)現(xiàn)無(wú)感知刷新Token的過(guò)程中,需要考慮以下幾個(gè)方面:
- 如何判斷Token是否過(guò)期?
- 如何在Token過(guò)期時(shí)自動(dòng)使用Refresh Token獲取新的Access Token?
- 如何處理Refresh Token的安全問(wèn)題?
下面將介紹如何實(shí)現(xiàn)無(wú)感知刷新Token的具體步驟。
實(shí)現(xiàn)步驟
步驟一:獲取Access Token和Refresh Token
在認(rèn)證成功后,需要將Access Token和Refresh Token發(fā)送給客戶端。Access Token用于訪問(wèn)受保護(hù)的API,Refresh Token用于獲取新的Access Token。可以使用JWT(JSON Web Token)或OAuth2(開(kāi)放授權(quán))等方式實(shí)現(xiàn)認(rèn)證。
在JWT中,可以使用如下代碼生成Access Token和Refresh Token:
const accessToken = jwt.sign({userId: '123'}, 'ACCESS_TOKEN_SECRET', {expiresIn: '15m'});
const refreshToken = jwt.sign({userId: '123'}, 'REFRESH_TOKEN_SECRET', {expiresIn: '7d'});
步驟二:在請(qǐng)求中攜帶Access Token
在每個(gè)需要認(rèn)證的API請(qǐng)求中,需要在請(qǐng)求頭中攜帶Access Token,如下所示:
GET /api/user HTTP/1.1 Host: example.com Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
在前端中,可以使用Axios等庫(kù)設(shè)置請(qǐng)求頭:
axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
步驟三:攔截401 Unauthorized響應(yīng)
在服務(wù)器返回401 Unauthorized響應(yīng)時(shí),說(shuō)明Access Token已經(jīng)過(guò)期,需要使用Refresh Token獲取新的Access Token??梢允褂肁xios攔截器或Fetch API的中間件實(shí)現(xiàn)攔截。
在Axios中,可以使用如下代碼實(shí)現(xiàn)攔截器:
axios.interceptors.response.use(response => {
return response;
}, error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true; //防止無(wú)限調(diào)用
return axios.post('/api/refresh_token', {refreshToken})
.then(response => {
const { access_token, refresh_token } = response.data;
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
originalRequest.headers.Authorization = `Bearer ${access_token}`;
return axios(originalRequest);
});
}
return Promise.reject(error);
});
在Fetch中,可以使用如下代碼實(shí)現(xiàn)中間件:
function authMiddleware(request) {
const access_token = localStorage.getItem('access_token');
if (access_token) {
request.headers.set('Authorization', `Bearer ${access_token}`);
}
return request;
}
function tokenRefreshMiddleware(response) {
if (response.status === 401) {
const refreshToken = localStorage.getItem('refresh_token');
return fetch('/api/refresh_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refreshToken })
}).then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Refresh Token failed');
}).then(data => {
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);
return Promise.resolve('refreshed');
}).catch(error => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
return Promise.reject(error);
});
}
return Promise.resolve('ok');
}
fetch('/api/user', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
middleware: [authMiddleware, tokenRefreshMiddleware]
}).then(response => {
console.log(response);
}).catch(error => {
console.error(error);
});
在上述代碼中,使用Axios或Fetch攔截器攔截401 Unauthorized響應(yīng),如果發(fā)現(xiàn)Access Token已經(jīng)過(guò)期,則發(fā)送Refresh Token請(qǐng)求獲取新的Access Token,并將新的Access Token設(shè)置到請(qǐng)求頭中,重新發(fā)送請(qǐng)求。
步驟四:服務(wù)器處理Refresh Token請(qǐng)求
在服務(wù)器端,需要編寫(xiě)API處理Refresh Token請(qǐng)求,生成新的Access Token,并返回給客戶端。
在JWT中,可以使用如下代碼生成新的Access Token:
const accessToken = jwt.sign({userId: '123'}, 'ACCESS_TOKEN_SECRET', {expiresIn: '15m'});
在刷新Token時(shí),需要驗(yàn)證Refresh Token的合法性,可以使用如下代碼驗(yàn)證Refresh Token:
try {
const payload = jwt.verify(refreshToken, 'REFRESH_TOKEN_SECRET');
const accessToken = jwt.sign({userId: payload.userId}, 'ACCESS_TOKEN_SECRET', {expiresIn: '15m'});
const refreshToken = jwt.sign({userId: payload.userId}, 'REFRESH_TOKEN_SECRET', {expiresIn: '7d'});
res.json({access_token: accessToken, refresh_token: refreshToken});
} catch (err) {
res.sendStatus(401);
}
在上述代碼中,使用JWT的verify方法驗(yàn)證Refresh Token的合法性,如果驗(yàn)證成功,則生成新的Access Token和Refresh Token,并返回給客戶端。
步驟五:設(shè)置定時(shí)刷新Token
為了避免Access Token過(guò)期時(shí)間太長(zhǎng),可以設(shè)置定時(shí)刷新Token的功能??梢允褂枚〞r(shí)器或Web Workers等方式實(shí)現(xiàn)定時(shí)刷新Token。在每次刷新Token時(shí),需要重新獲取新的Access Token和Refresh Token,并保存到客戶端。
function refreshToken() {
const refreshToken = localStorage.getItem('refresh_token');
axios.post('/api/refresh_token', {refreshToken})
.then(response => {
const { access_token, refresh_token } = response.data;
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
})
.catch(error => {
console.error(error);
});
}
setInterval(refreshToken, 14 * 60 * 1000); // 每14分鐘刷新Token
在上述代碼中,使用定時(shí)器每14分鐘刷新Token。在刷新Token成功后,將新的Access Token和Refresh Token保存到客戶端,并將新的Access Token設(shè)置到請(qǐng)求頭中。
安全性考慮
在實(shí)現(xiàn)無(wú)感知刷新Token的過(guò)程中,需要考慮到Refresh Token的安全性問(wèn)題。因?yàn)镽efresh Token具有長(zhǎng)期的有效期限,一旦Refresh Token被泄露,攻擊者就可以使用Refresh Token獲取新的Access Token,從而繞過(guò)認(rèn)證機(jī)制,訪問(wèn)受保護(hù)的API。
為了增加Refresh Token的安全性,可以考慮以下幾種措施:
- 將Refresh Token保存在HttpOnly Cookie中,可以避免在客戶端被JavaScript獲?。?/li>
- 對(duì)Refresh Token進(jìn)行加密或簽名,可以增加其安全性。
- 將Refresh Token保存在后端,前端通過(guò)接口和后端交互,實(shí)現(xiàn)刷新Access Token。
以上就是無(wú)感知刷新Token的詳細(xì)內(nèi)容,更多關(guān)于無(wú)感知刷新Token的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序 詳解下拉加載與上拉刷新實(shí)現(xiàn)方法
這篇文章主要介紹了微信小程序 詳解下拉加載與上拉刷新實(shí)現(xiàn)方法的相關(guān)資料,這里介紹了兩種實(shí)現(xiàn)方法,需要的朋友可以參考下2017-01-01
JS前端使用canvas實(shí)現(xiàn)擴(kuò)展物體類和事件派發(fā)
這篇文章主要為大家介紹了JS前端使用canvas實(shí)現(xiàn)擴(kuò)展物體類和事件派發(fā)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
微信小程序 實(shí)現(xiàn)點(diǎn)擊添加移除class
這篇文章主要介紹了 微信小程序 實(shí)現(xiàn)點(diǎn)擊添加移除class的相關(guān)資料,需要的朋友可以參考下2017-06-06
微信小程序中頁(yè)面FOR循環(huán)和嵌套循環(huán)
這篇文章主要介紹了微信小程序中頁(yè)面FOR循環(huán)和嵌套循環(huán)的相關(guān)資料,需要的朋友可以參考下2017-06-06
微信公眾號(hào) 客服接口的開(kāi)發(fā)實(shí)例詳解
這篇文章主要介紹了微信公眾號(hào) 客服接口的開(kāi)發(fā)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2016-09-09
JavaScript架構(gòu)搭建前端監(jiān)控如何采集異常數(shù)據(jù)
這篇文章主要為大家介紹了JavaScript架構(gòu)搭建前端監(jiān)控如何采集異常數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06

