咱們在實際項目中選用了JWT
這種認證方式.前端
簡單瞭解JWT
ios
JWT
即Json Web Token
, 用戶登錄後, 將非私密用戶信息放置在token
中攜帶給前端並加密, 以後每一筆請求攜帶token
, 後端解密token
便可取得用戶信息axios
最大好處: 後端無狀態, 能夠平滑橫向擴張, 且
token
較難解密後端
最大弊端: 後端較難控制
token
失效跨域
是否須要前端刷新令牌?併發
其實並非必定的測試
Cookie
+HttpOnly
進行令牌傳遞, 可讓前端無需操做任何令牌, 當令牌過時後主動由後端刷新並放回Cookie
便可Set-Cookie
時, Chrome
瀏覽器會發生沒法正常處理Cookie
的問題, 所以放棄Cookie
傳遞, 使用請求頭攜帶token
第一張圖是
Chrome
, 第二張圖是FireFox
, 相同請求網站
所以選用localStorage
存儲token
, 由前端處理放在請求頭中進行認證ui
最後一個問題: 因爲token
有效期短, 須要有人刷新token
小插曲: 其實能夠交給後端刷新令牌, 當
token
過時後, 後端刷新而後放回請求頭, 前端主動根據返回請求頭進行更新便可. 問題在後端遇到併發時token
會混亂.
最終緣由由前端刷新: 吵不事後端, 只能接下需求_(:з」∠)_
http
請求插件 不過多介紹, 選用了axios
axios
使用介紹 主要使用了攔截器interceptors
, 因爲使用了Promise
, 能夠隨意在請求先後進行各類延時操做.
登錄後將登錄token
儲存
axios
請求前將全部token
放入請求頭
退出登陸時清空token
存儲
當任意接口返回401
時, 嘗試刷新token
, 若成功, 則更新token
儲存, 若失敗則跳轉登錄
因爲併發的存在, 須要考慮如下狀況:
token
, 將此請求攔截, 並在成功刷新後, 更新請求頭並從新發送401
請求時, 若已經在嘗試刷新token
, 將此請求攔截, 並在成功刷新後, 更新請求頭並從新發送加載頁面時, 嘗試加載token
登陸後,將token
存儲進localStorage
, 並更新全部axios
實例的默認請求頭
null
退出登陸時, 將localStorage
的token
清除, 並清除全部axios
實例的默認請求頭
axios
請求失敗且狀態碼爲401
時
刷新Token完成
事件
token刷新成功
時, 將該筆請求更新token
, 而後從新發送token刷新失敗
時, 將該筆請求返回失敗, 交由Promise:reject
進一步處理token
請求時, 嘗試刷新token
, 並在刷新後觸發刷新Token完成
事件, 若刷新成功, 則將token
存儲進localStorage
, 並更新全部axios
實例的默認請求頭axios
請求前, 檢查是否正在刷新token
, 若正在刷新token
, 註冊一個刷新Token完成
事件
token刷新成功
時, 將該筆請求更新token
, 而後繼續發送token刷新失敗
時, 將該筆請求返回失敗, 拋棄請求加載頁面時, 從localStorage
中獲取token
私有全局變量
isRefreshing: Boolean
是否正在刷新token
RefreshEvent: EventEmitter
刷新事件分發器instances: AxiosInstance[]
全部封裝好的axios
實例,
注意最好默認包含
Axios
即默認實例
REFRESH_URL: String
刷新URL封裝方法
token
function setRefreshToken(refreshToken) {
if (refreshToken !== undefined) {
// 若攜帶參數, 則塞入localStorage中更新
localStorage.setItem('ompJwtRefreshToken', refreshToken)
} else {
// 若沒有攜帶參數, 則從localStorage中加載, 注意防範XSS攻擊
refreshToken = (
localStorage.getItem('ompJwtRefreshToken', refreshToken) || ''
).replace(/[^.\-_a-zA-Z0-9]/g, '')
}
// 這裏instances包含全部axios實例
instances.forEach(instance => {
// 設置默認請求頭
instance.defaults.headers['x-client-refresh-token'] = refreshToken
})
return refreshToken
}
複製代碼
token
插播小廣告: 個人掘金主頁
function tryToRefreshToken() {
// 約全局變量isRefreshing: 是否正在刷新token
isRefreshing = true
let refreshToken = localStorage.getItem('ompJwtRefreshToken') || ''
refreshToken = refreshToken.replace(/[^.\-_a-zA-Z0-9]/g, '')
// 沒有refreshToken是沒法刷新token的, 直接失敗
// 這裏使用事件分發機制處理, 返回false表示刷新失敗
if (!refreshToken) return RefreshEvent.emit('refreshEnd', false)
// ^_^ 友好提示, 防止用戶覺得刷新中點擊按鈕沒有反應
Notice.open({
title: '正在主動刷新, 嘗試繼續登錄中……',
duration: 0,
name: 'refresh'
})
axios
.get(REFRESH_URL)
.then(res => {
// 刷新成功
Notice.close('refresh')
Notice.open({
title: '刷新成功, 將自動繼續您以前的操做~'
})
// 注意觸發事件
RefreshEvent.emit('refreshEnd', true)
})
.catch(e => {
// 刷新失敗
RefreshEvent.emit('refreshEnd', false)
Notice.close('refresh')
Notice.open({
title: '刷新失敗, 將自動爲您跳轉登陸頁'
})
router.push({name: LOGIN_PAGE})
return Promise.reject(e)
})
}
複製代碼
function preRequestInterceptor(config) {
// 當正在刷新token時, 延時請求, 直到刷新完成
if (isRefreshing && config.url !== REFRESH_URL) {
// 經過返回Promise進行延遲操做
return new Promise((resolve, reject) => {
// 註冊事件
RefreshEvent.once('refreshEnd', result => {
// 注意resolve(config)才能繼續請求
// 注意config中已經包含舊的token了, 而且不會自動刷新, 須要手動從新設置下
if (result) {
config.headers['x-client-refresh-token'] = setRefreshToken()
resolve(config)
}
// 這裏建議reject封裝後的東西, 不然會出現reject形式不一致
else reject(config)
})
})
}
return config
}
複製代碼
function errorDeal(error) {
if (error && error.response) {
switch (error.response.status) {
// 通常會有其餘處理吧
case 401:
// 綁定事件
// 重發事件避免重複處理(其實不會出現這種狀況)
if (error.config._retry) return Promise.reject(error)
// 先註冊事件!!! 再觸發重試, 不然可能會註冊失敗哦~
const re = new Promise((resolve, reject) => {
// 一樣註冊事件, 用於延時請求
RefreshEvent.once('refreshEnd', result => {
if (result) resolve(error.config)
else reject(error)
})
}).then(config =>
// 兩個注意點:
// 1. 刷新token
// 2. 請使用Axios.create({})出的實例, 避免此請求重複一次錯誤處理, 那樣的話就會有兩次錯誤處理
config.headers['x-client-refresh-token'] = setRefreshToken()
axiosRetry.request({
...config,
// 綁定一些私有屬性方便大家使用
_retry: true
})
)
if (!isRefreshing) tryToRefreshToken()
// 注意返回Promise
return re
}
}
return Promise.reject(error)
}
複製代碼
在各個地方觸發各類方法:
// 定義變量喲~
// 註冊刷新結束時間, 解除刷新態, 路由處理
RefreshEvent.on('refreshEnd', result => {
isRefreshing = false
if (!result) {
route.push({
name: 'login'
})
}
})
// 綁定攔截器
instances.forEach(instance => {
instance.interceptors.request.use(preRequestInterceptor)
instance.interceptors.response.use(undefined, errorDeal)
})
// 註冊重試實例(即不註冊攔截器)
const axiosRetry = axios.create({})
// 先觸發一下, 以便從localStorage中進行加載
setAccessToken()
setRefreshToken()
複製代碼
分享一下工做時縹緲的想法, 沒有而後了.
若是文中出現錯誤, 還請提出喲, 我儘可能改~
插播小廣告: 個人掘金主頁
P.S. 座標: 南京, 性別: ♂, 聯繫方式: 41620F678
此文在掘金原創, 其餘網站請勿轉載.