Web 開發應該學習的 Token 登錄認證知識
基於 Cookie/Session 的認證方案
Cookie
由於HTTP
是一種無狀態的協議,服務器單從網絡連接上無從知道客戶身份。怎麼辦呢?就給客戶端們頒發一個通行證吧,每人一個,無論誰訪問都必須攜帶自己通行證。這樣服務器就能從通行證上確認客戶身份了。這就是。cookie
指的就是在瀏覽器裏面存儲的一種數據,僅僅是瀏覽器實現的一種數據存儲功能。cookie
的保存時間,可以自己在程序中設置。如果沒有設置保存時間,應該是一關閉瀏覽器,cookie
就自動消失。
Cookie
實際上是一小段的文本信息。客戶端請求服務器,如果服務器需要記錄該用戶狀態,就使用response
向客戶端瀏覽器頒發一個Cookie
。客戶端瀏覽器會把Cookie
保存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie
一同提交給服務器。服務器檢查該Cookie
,以此來辨認用戶狀態。服務器還可以根據需要修改Cookie
的內容。
注意:Cookie
功能需要瀏覽器的支持。如果瀏覽器不支持Cookie
(如大部分手機中的瀏覽器)或者把Cookie
禁用了,Cookie
功能就會失效。不同的瀏覽器採用不同的方式保存Cookie
。IE
瀏覽器會以文本文件形式保存,一個文本文件保存一個Cookie
。
- Cookie 的不可跨域名性
Cookie
具有不可跨域名性。根據Cookie
規範,瀏覽器訪問Google
只會攜帶Google
的Cookie
,而不會攜帶Baidu
的Cookie
。瀏覽器判斷一個網站是否能操作另一個網站Cookie
的依據是域名。
Session
Session
是另一種記錄客戶狀態的機制,不同的是Cookie
保存在客戶端瀏覽器中,而Session
保存在服務器上。客戶端瀏覽器訪問服務器的時候,服務器把客戶端信息以某種形式記錄在服務器上。這就是Session
。客戶端瀏覽器再次訪問時只需要從該Session
中查找該客戶的狀態就可以了。
如果說Cookie
機制是通過檢查客戶身上的 “通行證” 來確定客戶身份的話,那麼Session
機制就是通過檢查服務器上的 “客戶明細表” 來確認客戶身份。
session
也是類似的道理,服務器要知道當前發請求給自己的是誰。爲了做這種區分,服務器就要給每個客戶端分配不同的 “身份標識”,然後客戶端每次向服務器發請求的時候,都帶上這個 “身份標識”,服務器就知道這個請求來自於誰了。對於瀏覽器客戶端,大家都默認採用 cookie
的方式,保存這個 “身份標識”。
服務器使用session
把用戶的信息臨時保存在了服務器上,用戶離開網站後session
會被銷燬。這種用戶信息存儲方式相對cookie
來說更安。
可是session
有一個缺陷:如果web
服務器做了負載均衡,那麼下一個操作請求到了另一臺服務器的時候session
會丟失。
提示:Session
的使用比Cookie
方便,但是過多的Session
存儲在服務器內存中,會對服務器造成壓力。
Cookie 與 Session 的區別和聯繫
-
cookie
數據存放在客戶的瀏覽器上,session
數據放在服務器上; -
cookie
不是很安全,別人可以分析存放在本地的COOKIE
並進行COOKIE
欺騙,考慮到安全應當使用session
; -
session
會在一定時間內保存在服務器上。當訪問增多,會比較佔用你服務器的性能。考慮到減輕服務器性能方面,應當使用COOKIE
; -
單個 cookie 在客戶端的限制是 3K,就是說一個站點在客戶端存放的 COOKIE 不能超過 3K;
Cookie
和Session
的方案雖然分別屬於客戶端和服務端,但是服務端的session
的實現對客戶端的cookie
有依賴關係的,上面我講到服務端執行session
機制時候會生成session
的 id 值,這個id
值會發送給客戶端,客戶端每次請求都會把這個id
值放到http
請求的頭部發送給服務端,而這個id
值在客戶端會保存下來,保存的容器就是cookie
,因此當我們完全禁掉瀏覽器的cookie
的時候,服務端的session
也會不能正常使用。
基於 token 的認證方式
在大多數使用Web API
的互聯網公司中,tokens
是多用戶下處理認證的最佳方式。
以下幾點特性會讓你在程序中使用基於 Token 的身份驗證
-
無狀態、可擴展
-
支持移動設備
-
跨程序調用
-
安全
Token 的起源
在介紹基於Token
的身份驗證的原理與優勢之前,不妨先看看之前的認證都是怎麼做的。
- 基於服務器的驗證
我們都是知道HTTP
協議是無狀態的,這種無狀態意味着程序需要驗證每一次請求,從而辨別客戶端的身份。
在這之前,程序都是通過在服務端存儲的登錄信息來辨別請求的。這種方式一般都是通過存儲Session
來完成。
- 基於服務器驗證方式暴露的一些問題
1.Seesion
:每次認證用戶發起請求時,服務器需要去創建一個記錄來存儲信息。當越來越多的用戶發請求時,內存的開銷也會不斷增加。
- 可擴展性:在服務端的內存中使用
Seesion
存儲登錄信息,伴隨而來的是可擴展性問題。
3.CORS
(跨域資源共享):當我們需要讓數據跨多臺移動設備上使用時,跨域資源的共享會是一個讓人頭疼的問題。在使用Ajax
抓取另一個域的資源,就可以會出現禁止請求的情況。
4.CSRF
(跨站請求僞造):用戶在訪問銀行網站時,他們很容易受到跨站請求僞造的攻擊,並且能夠被利用其訪問其他的網站。
在這些問題中,可擴展行是最突出的。因此我們有必要去尋求一種更有行之有效的方法。
基於 Token 的驗證原理
基於 Token 的身份驗證是無狀態的,我們不將用戶信息存在服務器中。這種概念解決了在服務端存儲信息時的許多問題。NoSession
意味着你的程序可以根據需要去增減機器,而不用去擔心用戶是否登錄。
基於 Token 的身份驗證的過程如下:
-
用戶通過用戶名和密碼發送請求。
-
服務器端程序驗證。
-
服務器端程序返回一個帶簽名的
token
給客戶端。 -
客戶端儲存
token
, 並且每次訪問API
都攜帶Token
到服務器端的。 -
服務端驗證
token
,校驗成功則返回請求數據,校驗失敗則返回錯誤碼。
Tokens 的優勢
- 無狀態、可擴展
在客戶端存儲的Tokens
是無狀態的,並且能夠被擴展。基於這種無狀態和不存儲Session
信息,負載負載均衡器能夠將用戶信息從一個服務傳到其他服務器上。tokens
自己hold
住了用戶的驗證信息。
- 安全性
請求中發送token
而不再是發送cookie
能夠防止CSRF
(跨站請求僞造)。即使在客戶端使用cookie
存儲token
,cookie
也僅僅是一個存儲機制而不是用於認證。不將信息存儲在Session
中,讓我們少了對session
操作。
token
是有時效的,一段時間之後用戶需要重新驗證。
- 可擴展性
Tokens
能夠創建與其它程序共享權限的程序。
- 多平臺跨域
我們提前先來談論一下CORS
(跨域資源共享),對應用程序和服務進行擴展的時候,需要介入各種各種的設備和應用程序。
需要設置有效期嗎?
對於這個問題,我們不妨先看兩個例子。一個例子是登錄密碼,一般要求定期改變密碼,以防止泄漏,所以密碼是有有效期的;另一個例子是安全證書。SSL
安全證書都有有效期,目的是爲了解決吊銷的問題。所以無論是從安全的角度考慮,還是從吊銷的角度考慮,Token
都需要設有效期。
- 那麼有效期多長合適呢?
只能說,根據系統的安全需要,儘可能的短,但也不能短得離譜
- 然後新問題產生了,如果用戶在正常操作的過程中,
Token
過期失效了,要求用戶重新登錄…… 用戶體驗豈不是很糟糕?
一種方案,使用 Refresh Token
,它可以避免頻繁的讀寫操作。這種方案中,服務端不需要刷新 Token
的過期時間,一旦 Token
過期,就反饋給前端,前端使用 Refresh Token
申請一個全新Token
繼續使用。這種方案中,服務端只需要在客戶端請求更新 Token
的時候對 Refresh Token
的有效性進行一次檢查,大大減少了更新有效期的操作,也就避免了頻繁讀寫。當然 Refresh Token
也是有有效期的,但是這個有效期就可以長一點了,比如,以天爲單位的時間。
- 時序圖表示
使用 Token
和 Refresh Token
的時序圖如下:
1)登錄
2)業務請求
3)Token
過期,刷新 Token
上面的時序圖中並未提到 Refresh Token
過期怎麼辦。不過很顯然,Refresh Token
既然已經過期,就該要求用戶重新登錄了。
項目中使用 token 總結
使用基於 Token
的身份驗證方法,在服務端不需要存儲用戶的登錄記錄。大概的流程是這樣的:
-
前端使用用戶名跟密碼請求首次登錄
-
後服務端收到請求,去驗證用戶名與密碼是否正確
-
驗證成功後,服務端會根據用戶
id
、用戶名、定義好的祕鑰、過期時間生成一個Token
,再把這個Token
發送給前端 -
前端收到 返回的
Token
,把它存儲起來,比如放在Cookie
裏或者Local Storage
裏
export interface User { token: string; userInfo: UserInfo | any; companyInfo: CompanyInfo | any; resources?: string[]; }
save(key: string, value: any, storageType ?: StorageType) { return this.storageService.put( { pool: key, key: 'chris-app', storageType: StorageType.localStorage }, value ); } this.storageService.save(CACHE_USER_KEY, user);
- 前端每次路由跳轉,判斷
localStroage
有無token
,沒有則跳轉到登錄頁。有則請求獲取用戶信息,改變登錄狀態;6. 前端每次向服務端請求資源的時候需要在請求頭裏攜帶服務端簽發的Token
HttpInterceptor => headers = headers.set('token', this.authService.getToken());
-
服務端收到請求,然後去驗證前端請求裏面帶着的
Token
。沒有或者token
過期,返回401
。如果驗證成功,就向前端返回請求的數據。 -
前端得到
401
狀態碼,重定向到登錄頁面。
HttpInterceptor => 401: '用戶登陸狀態失效,請重新登陸。'
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/u8vRftCscsrJ9NN389pHMA