一、Token
token 是一串字符串,通常因為作為鑒權憑據,最常用的使用場景是 API 鑒權。
1. API 鑒權
那么 API 鑒權一般有幾種方式呢?我大概整理了如下:
cookie + session
和平常 web 登陸一樣的鑒權方式,很常見,不再贅述。
HTTP Basic
將賬號和密碼拼接然后 base64 編碼加到 header 頭中。很顯然,因為賬號和密碼幾乎是『明文』傳輸的,而且每次請求都傳,安全性可想而知。
HTTP Digest
將賬號和密碼加上其他一些信息拼接然后取摘要加到 header 頭中。這個安全性比上面要好一點,因為如果是取摘要的話,即使信息段被截取,也無法輕易破解出來(當然也是有破解的可能)。
不過其實最大的問題還是:每次請求都要對賬號、密碼取一次摘要,也就是說每次請求都要有賬號和密碼,也就是說賬號和密碼要么緩存一下,要么就每次請求要去用戶輸一次密碼,這樣顯然不合適。同樣,上面的 Basic 也存在這樣的問題。
Token
token 通過一次登錄驗證,得到一個鑒權字符串,然后以后帶着這個鑒權字符串進行后續操作,這樣就可以解決每次請求都要帶賬號密碼的問題,而且也不需要反復使用賬號和密碼。
所以我們接下來主要探討 token 相對於 Cookie + Session 的認證方式有什么優勢呢?
2. Token 的優勢
token 相對於 Cookie + Session 的優點,主要有下面兩個:
CSRF 攻擊
這個原理不多做介紹,構成這個攻擊的原因,就在於 Cookie + Session 的鑒權方式中,鑒權數據(cookie 中的 session_id)是由瀏覽器自動攜帶發送到服務端的,借助這個特性,攻擊者就可以通過讓用戶誤點攻擊鏈接,達到攻擊效果。而 token 是通過客戶端本身邏輯作為動態參數加到請求中的,token 也不會輕易泄露出去,因此 token 在 CSRF 防御方面存在天然優勢。
適合移動應用
移動端上不支持 cookie,而 token 只要客戶端能夠進行存儲就能夠使用,因此 token 在移動端上也具有優勢。
3. Token 的種類
一般來說 token 主要三種:
- 自定義的 token:開發者根據業務邏輯自定義的 token
- JWT:JSON Web Token,定義在 RFC 7519 中的一種 token 規范
- Oauth2.0:定義在 RFC 6750 中的一種授權規范,但這其實並不是一種 token,只是其中也有用到 token
以上,我仔細介紹了 API 常用的鑒權方式,以及 token 相對於 cookie + session 的優點。然后接下來仔細分析 JWT。
二、JWT 的組成和優勢
JWT 全稱 JSON Web Tokens ,是一種規范化的 token。可以理解為對 token 這一技術提出一套規范,是在 RFC 7519 中提出的。
1. 組成
一個 JWT token 是一個字符串,它由三部分組成,頭部、載荷與簽名,中間用 .
分隔,例如:xxxxx.yyyyy.zzzzz
頭部(header)
頭部通常由兩部分組成:令牌的類型(即 JWT)和正在使用的簽名算法(如 HMAC SHA256 或 RSA.)。
例如:
{ "alg": "HS256", "typ": "JWT" }
然后用 Base64Url
編碼得到頭部,即 xxxxx
。
載荷(Payload)
載荷中放置了 token
的一些基本信息,以幫助接受它的服務器來理解這個 token
。同時還可以包含一些自定義的信息,用戶信息交換。
載荷的屬性也分三類:
- 預定義(Registered)
- 公有(public)
- 私有(private)
預定義的載荷
{ "sub": "1", "iss": "http://localhost:8000/auth/login", "iat": 1451888119, "exp": 1454516119, "nbf": 1451888119, "jti": "37c107e4609ddbcc9c096ea5ee76c667", "aud": "dev" }
這里面的前 7 個字段都是由官方所定義的,也就是預定義(Registered claims)的,並不都是必需的。
- iss (issuer):簽發人
- sub (subject):主題
- aud (audience):受眾
- exp (expiration time):過期時間
- nbf (Not Before):生效時間,在此之前是無效的
- iat (Issued At):簽發時間
- jti (JWT ID):編號
公有的載荷
在使用 JWT 時可以額外定義的載荷。為了避免沖突,應該使用 IANA JSON Web Token Registry 中定義好的,或者給額外載荷加上類似命名空間的唯一標識。
私有載荷
在信息交互的雙方之間約定好的,既不是預定義載荷也不是公有載荷的一類載荷。這一類載荷可能會發生沖突,所以應該謹慎使用。
將上面的 json
進行 Base64Url
編碼得到載荷,,即 yyyyy
。
關於載荷的理解:
這里三種載荷的定義應該明確的一點是 —— 對於后兩種載荷,它並非定義了載荷的種類,然后讓你去選用哪種載荷,而是對你可能會定義出來的載荷做一個分類。
比如你定義了一個
admin
載荷,這個載荷按其分類應該是私有載荷,可能會和其他人定義的發生沖突。但如果你加了一個前綴(命名空間),如namespace-admin
,那么這應該就算一個公有載荷了。(但其實標准並沒有定義怎么去聲明命名空間,所以嚴格來說,還是可能會沖突)但是在現實中,團隊都是約定好的了要使用的載荷,這樣的話,好像根本不存在沖突的可能。那為什么文檔要這么定義呢?我的理解是,RFC 是提出一種技術規范,出發點是一套通用的規范,考慮的范圍是所有開發者,而不僅僅局限於一個開發者團隊。就像用 token 做認證已經是很常見的技術了,但是 JWT 的提出就相當於提出了一套較為通用的技術規范。既然是為了通用,那么考慮在大環境下的沖突可能性也是必須的。
簽名(Signature)
簽名時需要用到前面編碼過的兩個字符串,如果以 HMACSHA256
加密,就如下:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
加密后再進行 base64url
編碼最后得到的字符串就是 token
的第三部分 zzzzz
。
組合便可以得到 token:xxxxx.yyyyy.zzzzz
。
簽名的作用:保證 JWT 沒有被篡改過,原理如下:
HMAC 算法是不可逆算法,類似 MD5 和 hash ,但多一個密鑰,密鑰(即上面的 secret)由服務端持有,客戶端把 token 發給服務端后,服務端可以把其中的頭部和載荷再加上事先共享的 secret 再進行一次 HMAC 加密,得到的結果和 token 的第三段進行對比,如果一樣則表明數據沒有被篡改。
Hash-based Message Authentication Code
2. 使用
JWT 的使用有兩種方式:
- 加到 url 中:
?token=你的token
- 加到 header 中,建議用這種,因為在 https 情況下更安全:
Authorization:Bearer 你的token
JWT 在客戶端的存儲有三種方式:
- LocalStorage
- SessionStorage
- Cookie [不能設置 HTTPonly]
但是最推薦的還是第三種,因為第一二種存在跨域讀取限制,而 Cookie 使用不同的跨域策略
因為沒開 HTTPonly,所以要注意防范 XSS 漏洞。
Cookie 的跨域策略
子可以讀父,但是父不可以讀子,兄弟之間不能互相訪問。
a.xxx.com 和 b.xxx.com 可以讀 xxx.com,但是 a.xxx.com 和 b.xxx.com 不能互相讀取,xxx.com 也不能讀 a.xxx.com 和 b.xxx.com 的。
你可能會想:存 Cookie 那我不是又變得和 cookie + session 一樣了嗎?
其實不然,因為存 cookie 在這只是用到了其存儲機制,而沒有利用其去鑒權。也就是說我只是簡單存一下,並沒有期望瀏覽器帶上去 token 去鑒權,將 token 加入請求這部分操作還是我手動進行的。
3. 相對於一般 token 的優點
既然 JWT 也是一種 token,那么它相對於普通的 token 有何優點呢?
無狀態
因為 JWT 的有效期完全與其載荷中編碼的過期時間,服務端不維護任何狀態,因此 JWT 『一般』是『無狀態』的(為什么是一般,后面會仔細說)。無狀態最大的優勢在於三點:
- 節省服務器的資源:因為服務端無需維護一個狀態,因此能夠節省服務端原先保存這些狀態所花費的資源
- 適合分布式:因為服務端無需維護狀態,因此如果服務端是多台服務器組成的分布式集群,那么無需像『有狀態』一樣互相同步各自的狀態。
- 時間換空間:因為 token 的校驗時通過簽名校驗來進行的,簽名校驗消耗的是 CPU 時間,而『有狀態』是需要通過客戶端提供的憑據對服務端現有的狀態進行一次查詢,消耗的是 I/O 和內存、磁盤空間。通常對於一個 Web 服務來說,其屬於 I/O 密集型,因此通過時間換空間這一操作,可以提高整體的硬件使用率。
編碼數據
因為 JWT 能夠在載荷中編碼了部分信息,所以如果把常用數據編碼進去的話,能夠大大減少數據庫的查詢次數,不過有兩點需要額外注意的:
- 載荷信息是明文編碼的,所以不能編碼敏感信息在里面,如果要編碼可以先加密再編碼進去
- token 在每次請求時都會進行傳輸,所以載荷中不能編碼過多的信息,否則會降低傳輸效率
所以 JWT 就有四個優點了:
- 防 CSRF
- 適合移動應用
- 無狀態
- 編碼數據
前兩個是 token 的優勢,后兩個是 JWT 獨特的優勢。
三、JWT 的安全問題
既然主要使用場景是鑒權,那么安全問題就是不得不考慮的問題了。下面對 JWT 可能需要的安全問題都進行一次深入的探討並尋求最佳的解決方案。
1. 重放攻擊
重放攻擊是通過把原先的包進行一次重放來進行攻擊的手段。需要先明確是的 cookie + session 也是存在重放攻擊的問題的。
常用的防范重放攻擊的措施主要有以下幾種:
timestamp
在請求中夾帶一個時間戳,設置較短的有效期,如果一個新來的請求的請求時間超過了請求中的有效期,則認為無效。但是這種策略也存在問題,即如果一個黑客『眼疾手快』在有效期以內將你的包進行了重放, 那就來攻擊成功。
這種策略對應到 JWT 中就是給 token 設置一個較短的有效期。
nonce
在請求中夾帶一個隨機字符串,這個字符串傳送到客戶端后即存入客戶端的黑名單中,如果一個新來的請求其中存在的隨機字符串已經在黑名單中則認為無效。但是顯然,這個策略存在巨大的問題:服務端需要維護一個黑名單庫,這個庫的大小會隨着業務運行的時間而變得無比巨大,從而嚴重影響效率。
這種策略對應到 JWT 中就是給 token 設置一個黑名單,但是不設置有效期。
timestamp + nonce
在請求中夾帶一個隨機字符串和一個時間戳,如果一個新來的請求,其隨機字符串已經在黑名單中則認為無效,或者一個請求的的請求時間超過了其有效期,則也認為其無效。這樣黑名單的范圍只需設置為時間戳策略的有效期范圍即可。
這種策略對應到 JWT 中就是給 token 既設置一個黑名單,又設置一個有效期。
挑戰 - 應答
這個其實和 timestamp + nonce 策略一樣,只是隨機字符串是有服務端生成給客戶端的,客戶端攜帶服務端所給的隨機串來請求。這樣有什么好處呢?服務端可以通過一個加密算法來生成這個串,使其和時間戳相關,同時客戶端又無法偽造。這樣就不需要維護黑名單了。同樣也是時間換空間的策略。但是顯然每次或幾次請求就要進行一次與預請求以得到隨機串,並不是特別方便,造成的額外消耗也有待考量。
序列號
通過在請求中嵌入一個序列號,每次請求依次加一,如果一個請求的序列號早已用過,則認為無效。但是這個要用邏輯額外一個全局序列號,並不是特別方便。
HTTPS
終極解決方案了,HTTPS 在握手過程中會自動維護一個隱式序列號,解決了上面要自己維護序列號的問題。
注意:以上均沒有討論客戶端主動重放的問題,有興趣的同學可以自己研究一下。
2. token 被盜
因為 token 中包含了登陸狀態,因此一旦 token 被盜,那么就會被人盜用身份。那么 token 針對被盜的防范措施整理如下:
- 使用 HTTPS 傳輸:從傳輸層的角度解決問題
- HTTPOnly:從存儲層的角度解決問題,防止 XSS 攻擊竊取 cookie,但是這種方案其實存在問題,因為這樣 js 就無法讀取 token 並把它加到 header 頭中了。所以不開啟 HTTPOnly 的話必須要額外注意防范 XSS 攻擊。
- 在 token 中嵌入客戶端指紋:通過客戶端指紋,即使黑客盜取了你的 cookie,他也無法用你的 cookie 進行請求。
- 設置較短的 token 有效期:這樣如果 token 被盜,只要超過一定時限就無法使用。
四、JWT 的其他問題
除了安全問題,JWT 還有許多其他需要考慮的問題。
1. 注銷問題
因為 JWT 是無狀態的,所以它的有效期完全由其本身決定,也就是說服務端無法讓一個 token 失效。顯然這是一個比較大的問題,對此也有諸多解決方案:
1.1 客戶端主動注銷
客戶端直接刪除存儲 token 的 cookie
這種方案最為簡單,操作的結果是無論客戶端還是服務端都沒有這個 token,可問題是,這個 token 並沒有真正不可使用,而是處於一個游離態。
黑名單策略
客戶端攜帶要注銷的 token 訪問一個注銷接口,服務端把 token 加入一個黑名單。
此策略是否會出現黑名單過大的問題?
答案是不會,因為黑名單只需維護本身沒有過期但又要使其無效的 token,過期的 token 就可以不用存在黑名單了。
1.2 服務端主動注銷 \ 用戶修改密碼
把 token 和 uuid 用 key-value 對存儲在 redis
這種方案看上去沒問題,但是實際上,相當於自己實現了一次 cookie + session,JWT 就失去了『無狀態』這一特性,從也會失去『無狀態』特性帶來的一系列的優點。
讓每個用戶都有一個 secret
前面講到簽發 token 的時候用到了 secret ,這種策略的思想就是讓每個用戶都有一個 secret,注銷一個用戶的時候修改其 secret,即可使其前面簽發的 token 無法通過校驗而失效。
這種策略上聽上去不需要維護一個狀態,但是實際上存在更大的問題。試想一下,第一種方案是通過 uuid 在已登錄用戶的 token 表中找到要注銷的 token 注銷。cookie + session 是通過 session_id 在已登錄的用戶的 session 表中找到其對應的 session 並刪除來注銷。而此方案是通過 uuid 在所有用戶(而非已登錄用戶)中找到對於的 secret 修改來注銷。這樣看來會發現效率更低,因為查找范圍更大了。
預黑名單
把要注銷的用戶的 uuid 和當前時間(TIME) 組成 key-value 對加入預黑名單,下次請求來時,若其 uuid 和黑名單中的對應,並且簽發時間在 TIME 之前,則將其注銷。這樣查找范圍就是未過期但又要注銷的用戶。並且在實現邏輯上這個預黑名單可以和簽名的黑名單做到一起。
關於黑名單策略的補充:
有人可能會覺得黑名單也是一種狀態,用這種策略實現的 JWT 並不能算純正的無狀態。這種說法沒錯,但是考慮每次要檢索的數據范圍可以得到下面一個關系:
未過期但要提前注銷的用戶或 token 數 < 所有已登錄用戶數 < 所有用戶數
此處的『 < 』基本可以看成『遠遠小於』,所以黑名單策略雖然也算有狀態,但是其維護的狀態數也是特別小的。
可見 『黑名單』策略能夠有效解決 JWT 的注銷問題。
2. 續簽問題
session 可以自動續簽,那 token 如何實現自動續簽呢?我們先仔細分析一下在 web 和 app 環境中,token 分別如何續簽。先具體分析 web 續簽和 app 續簽分別是什么樣的具體需求。
web
超過一段時間沒有請求,需要重新登錄,這個時間一般設置為 1-2 小時
app
超過一段較長的時間沒有請求,需要重新登錄,這個時間一般為 15-30 天
那這個需求可以如何實現呢?
2.1 方式一
- 服務端接管刷新
- token 設置一個『過期時間』
- token 過期后但是仍在『刷新時間』內時仍然可刷新
- token 過期后超過『刷新時間』就不能再刷新,需重新登錄
web
假設一個 token 的簽發時間為 12:00,需求為 2h 未進行請求就要重新登錄。則過期時間為 1h,刷新時間為 3h。
那么在 12:00 - 13:00 其都是可以正常使用的,如果在 13:00 - 15:00 進行請求,服務端自動換一個新 token 給客戶端,達成續簽。
如果 13:00 -15:00 之間沒有進行請求,而是在 15:00 之后進行的請求,那么判斷過期,需重新登錄。
這樣的話,最終的實現效果是:token 過期 2h 后需要重新登錄 ,而不是 token 2h 未使用需要重新登錄,導致的結果是,用戶是 2 - 3h 未進行請求,需要重新登錄。比設定的需求要多一個小時的不確定時間,但這也是沒辦法的辦法了,至於會不會對業務造成影響,看具體需求吧,大多數的情況還是不會的。
app
和 web 端類似,設置成更長的時間周期即可。
對使用 Laravel 開發並使用 tymon/jwt-auth 這個插件的開發者,有個必須要注意的地方。
此處進行 token 的刷新並不是通過 refresh
這個操作獲得新 token,因為這樣 token 在不斷的刷新過程中會達到一個刷新時間的上限。而上面的邏輯是每次都新簽發一個 token,只要不斷簽就能夠一直使用下去。 然后這里的舊 token 放入黑名單,黑名單有效期設置為『刷新時間』—— 3h。
當然如果開發者覺得這樣不斷簽就能夠一直使用不太好,那就可以設置更長的刷新時間,用 refresh
操作來獲取新 token,刷新時間保證每次登陸得到 token 后,即使每次及時續簽,最終也不會超過刷新時間。
然后這里又會出現一個新坑:
如果刷新時間設置為 14 天,過期時間設置為 2h。
token A 在 『 <= 14 天 』時刷新得到 token B,此時若再拿 token A 去請求刷新,肯定是不允許,否則 token 會出現『 1 變 N 』的問題,所以顯然必須設置一個黑名單去放這些已過期但是又已經刷新過的 token。而這個黑名單的有效期范圍應當為 token 的刷新期,即 14 天。然后你會發現對於每個用戶每次登陸,需要維護的黑名單 token 數目最大可達 14 * 24 / 2 = 168 個,黑名單變得很大。
所以,如果要使用
refresh
操作,刷新時間務必是過期時間的盡量小的倍數。
2.2 方式二
- 每次請求 token 都進行一次刷新
- token 設置一個過期時間
- token 過期后無法再刷新
- token 沒必要設置刷新時間了
web
假設一個 token 的簽發時間為 12:00,需求為 2h 未進行請求即過期。則設置有效期 2h,不需要設置刷新期。那么每次請求都會把一個 token 換成一個新 token。如果 2h 沒有進行請求,那么上一次請求的到的 token 就會過期,需要重新登錄。同樣是不斷簽就能一直使用下去。
如果想要和上面一樣,不希望永久續簽,則設置一個刷新時間即可。這個刷新時間不會導致進一步膨脹。
app
和 web 端類似,設置更長時間即可。
然后又到了問題時間:
每次都刷新 token,帶來的性能影響如何?
以前每次請求,需要進行一次 token 簽名校驗,而現在是要簽發一個新 token,進行的都是一次簽名運算,那么運算量即從 n 變成 2n。
其次,每次刷新都要把舊 token 加入黑名單,會導致黑名單特別大,遠遠比方式一的設置刷新期大。
每次都刷新 token,並發請求時會不會因為 token 刷新而導致只有一個請求成功?
答案是確實會導致這個問題,怎么解決呢?設置一個寬限時間,每次 token 刷新后,原來邏輯應該是立刻不可用,現在設置一個寬限時間,讓其在 n 秒之內仍然可用即可。
總之,這種策略會導致花費的 CPU 運算翻倍,並導致巨大的黑名單,然后必須設置一個寬限時間以解決並發請求問題,至於寬限時間會不會帶來安全問題,微乎其微吧。
2.3 黑名單膨脹的解決方案
上面講到,對於方式一【限定不能一直續簽】,會導致巨大的黑名單,對於方式二,總會導致一個更加巨大的黑名單。那有沒有解決方案呢?當然是有的。
我們可以這么想,既然一個 token 進行了刷新,那么簽發時間在這次刷新之前的即可認為無效。於是,和上面的『預黑名單』策略類似,我刷新時不是把一個 token 加入黑名單,而是把 uuid-refresh_time 組成 key-vakue 對加入黑名單,這樣針對每個用戶的每次登陸,要存儲到黑名單中的條目數就從 N 個變成了一個。
但是這樣還要考慮一個問題:就是一個用戶開兩個瀏覽器,在不同的時刻在同一個系統都登陸了(假設業務允許),那么一個瀏覽器的 token 刷新就可能會導致另一個瀏覽器登陸失效。所以存儲在黑名單中的 key-value 應該再加一個 key 以代表每次登陸,並且這個 key 要在 JWT 的載荷中隨着刷新一直傳承。
基於以上的優化,黑名單的大小變成了:每個用戶同時登陸的系統個數之和,就變的和 cookie + session 一樣了。
比如,A 系統(假設 2h 過期時間,14 天刷新時間),你用一個瀏覽器登陸了你的賬號,我用 Chrome 瀏覽器登陸了我的賬號,然后我又用 QQ 瀏覽器再登陸我的賬號,那么黑名單的大小就為 : 1 + 2 = 3
而對於方式一【限定不能一直續簽】,黑名單的大小(最大):168 + 168 * 2
而對於方式二,黑名單的大小為:你在 2h 內請求的次數 x ,我在 Chrome 瀏覽器請求的次數 y,我在 QQ 瀏覽器請求的次數 z 之和,即:x + y + z
2.4 總結
如果要解決續簽問題,方式一【可以一直續簽】是個比較好的解決方案,雖然會帶來一點小問題,但是並不會有太大的影響。方式二【限定不能一直續簽】和 每次刷新會讓黑名單的維護量和有狀態差不多,但是有更高的安全性。
3. token 有沒有必要每次刷新
我們先列舉每次刷新 token 的優缺點:
優點:
- 能夠實現續簽
- 能夠解決重放
- 更安全
缺點:
- 雙倍的 CPU 消耗
- 幾乎和有狀態一樣的空間消耗
- 必須設置寬限時間解決並發問題
上面討論過,『續簽』和『重放』都可以通過其他方式解決。只有『更安全』算半個痛點,為什么是半個痛點呢?因為如果采用 HTTPS 的話,那么盜取 token 的手段就只要以下幾種辦法:
- 破解 HTTPS
- 直接從你電腦上手抄過去
- XSS【前面說到為了能夠讓 js 讀取,不能設置 HTTPOnly】
只有第三種方法存在一點可能性。
所以,要不要每次刷新,還是根據各位的具體業務情況進行選擇吧。
五、JWT 適合用來做什么
1. 無狀態的 RESTful API
這個顯然很適合。
2. SSO 單點登錄
單點登錄必須要實現的:
- 會話管理:通過黑名單和預黑名單解決
- 續簽:通過簽名的解決方案解決
可見,對 JWT 部署一些額外邏輯(黑名單,續簽管理)即可讓 JWT 在大部分場景代替 cookie + session。
六、JWT 與 Oauth2.0
Oauth 2.0 是干嘛的不再贅述,它與 JWT 其實並不是一個層面的東西。Oauth2.0 是一個方便的第三方授權規范,而 JWT 是一個 token 結構規范。只是 JWT 常用來登陸鑒權,而 Oauth2.0 在授權時也涉及到了登陸,所以就比較容易搞混。
但是在此,我要說的是,Oauth 2.0 其實可以和 JWT 結合使用。
以下是一個常見的 Oauth2.0 登陸返回:
{ "access_token":"kag2geh11a3eh56e23hj", "expires_in":7200, "refresh_token":"jgko97cq4c8wn69j", "scope":"SCOPE" }
在 Oauth2.0 中,access_token
用來進行數據請求,而 refresh_token
用來刷新 access_token
。每次刷新,上一個 access_token 就會失效,而 access_token
和 refresh_token
顯然都沒有記錄任何狀態,所以必須為服務端進行狀態的維護。
把 JWT 和 Oauth2.0 結合后,可以得到這樣的返回:
{ "access_token":"xxx.yyy.zzz", "expires_in":7200, "refresh_token":"xxxxx.yyyyy.zzzzz", "scope":"SCOPE" }
進行結合后有如下優勢:
- Oauth2.0 的 token 也能夠實現無狀態(雖然也要用到黑名單)
- Oauth2.0 的 token 也能夠附帶部分常用數據
- 前面講到 JWT 續簽,在需要限定不能一直續簽的情形,可能會導致黑名單庫膨脹,但是和 Oauth2.0 結合,通過
refresh_token
的機制,讓黑名單庫中 token 的有效期從 『刷新時間』又變回『過期時間』,從而解決了這個問題。
七、關於 token 十件必須知道的事
這是我從 Auth0 組織的這篇文章 10 Things You Should Know about Tokens 整理過來的:
-
Token 獲取到后需要保存起來以便下次使用,可以選擇存儲在 localstorage /sessionstorage/cookie
-
Token 是包含有效期的,你必須部署一些邏輯來進行有效期的控制
-
localstorage /sessionstorage 的跨域限制較 cookie 更為嚴格,推薦使用 cookie
-
在你進行異步請求時,瀏覽器一般都會發送預檢請求(option),后端應對此部署相應的邏輯
-
使用 cookie 可以輕松處理一個文件下載請求,但是 token 一般都是通過 XHR 方式進行請求的,所以你必須部署額外的邏輯。比如生成一個實時 ticket ,以 ticket 進行訪問,然后校驗,重定向,最后下載文件。
-
處理 XSS 比處理 CSRF 更容易(這一點我實在沒看到他是什么個邏輯,大家可以去看看原文)
-
token 在每次請求時都會被編碼到請求中,所以請注意 token 的大小,不要編碼過多數據
-
如果在 token 中編碼敏感信息,請對 token 進行加密
-
JSON Web Token 可以用於 Oauth2.0 的 Bearer Token 中,賦予 Oauth2.0 無狀態的優勢
-
Token 不是銀彈,請根據實際業務需要進行選擇