上次在 《JSON Web Token - 在Web應用間安全地傳遞資訊》
中我提到了JSON Web Token可以用來設計單點登入系統。我嘗試用八幅漫畫先讓大家了解如何設計正常的使用者認證系統,然後再延伸到單點登入系統。
如果還沒有閱讀
,我強烈建議你花十分鐘閱讀它,了解JWT的生成過程和原理。https://blog.didispace.com/user-authentication-with-jwt/#%E7%94%A8%E6%88%B7%E8%AE%A4%E8%AF%81%E5%85%AB%E6%AD%A5%E8%B5%B0 使用者認證八步走
所謂使用者認證(Authentication),就是讓使用者登入,并且在接下來的一段時間内讓使用者通路網站時可以使用其賬戶,而不需要再次登入的機制。
小知識:可别把使用者認證和使用者授權(Authorization)搞混了。使用者授權指的是規定并允許使用者使用自己的權限,例如釋出文章、管理站點等。
首先,伺服器應用(下面簡稱“應用”)讓使用者通過Web表單将自己的使用者名和密碼發送到伺服器的接口。這一過程一般是一個HTTP POST請求。建議的方式是通過SSL加密的傳輸(https協定),進而避免敏感資訊被嗅探。

接下來,應用和資料庫核對使用者名和密碼。
核對使用者名和密碼成功後,應用将使用者的
id
(圖中的
user_id
)作為JWT Payload的一個屬性,将其與頭部分别進行Base64編碼拼接後簽名,形成一個JWT。這裡的JWT就是一個形同
lll.zzz.xxx
的字元串。
應用将JWT字元串作為該請求Cookie的一部分傳回給使用者。注意,在這裡必須使用
HttpOnly
屬性來防止Cookie被JavaScript讀取,進而避免
跨站腳本攻擊(XSS攻擊)。
在Cookie失效或者被删除前,使用者每次通路應用,應用都會接受到含有
jwt
的Cookie。進而應用就可以将JWT從請求中提取出來。
應用通過一系列任務檢查JWT的有效性。例如,檢查簽名是否正确;檢查Token是否過期;檢查Token的接收方是否是自己(可選)。
應用在确認JWT有效之後,JWT進行Base64解碼(可能在上一步中已經完成),然後在Payload中讀取使用者的id值,也就是
user_id
屬性。這裡使用者的
id
為1025。
應用從資料庫取到
id
為1025的使用者的資訊,加載到記憶體中,進行ORM之類的一系列底層邏輯初始化。
應用根據使用者請求進行響應。
和Session方式存儲id的差異
Session方式存儲使用者id的最大弊病在于要占用大量伺服器記憶體,對于較大型應用而言可能還要儲存許多的狀态。一般而言,大型應用還需要借助一些KV資料庫和一系列緩存機制來實作Session的存儲。
而JWT方式将使用者狀态分散到了用戶端中,可以明顯減輕服務端的記憶體壓力。除了使用者id之外,還可以存儲其他的和使用者相關的資訊,例如該使用者是否是管理者、使用者所在的分桶(見[《你所應該知道的A/B測試基礎》一文](/2015/08/27/introduction-to-ab-testing/)等。
雖說JWT方式讓伺服器有一些計算壓力(例如加密、編碼和解碼),但是這些壓力相比磁盤I/O而言或許是半斤八兩。具體是否采用,需要在不同場景下用資料說話。
https://blog.didispace.com/user-authentication-with-jwt/#%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95 單點登入
Session方式來存儲使用者id,一開始使用者的Session隻會存儲在一台伺服器上。對于有多個子域名的站點,每個子域名至少會對應一台不同的伺服器,例如:
- www.taobao.com
- nv.taobao.com
- nz.taobao.com
- login.taobao.com
是以如果要實作在
login.taobao.com
登入後,在其他的子域名下依然可以取到Session,這要求我們在多台伺服器上同步Session。
使用JWT的方式則沒有這個問題的存在,因為使用者的狀态已經被傳送到了用戶端。是以,我們隻需要将含有JWT的Cookie的
domain
設定為頂級域名即可,例如
Set-Cookie: jwt=lll.zzz.xxx; HttpOnly; max-age=980000; domain=.taobao.com
注意
domain
必須設定為一個點加頂級域名,即
.taobao.com
。這樣,taobao.com和*.taobao.com就都可以接受到這個Cookie,并擷取JWT了。