剛剛,給學妹普及了登錄的兩大絕學

  • 2020 年 9 月 28 日
  • 筆記

今天跟大家聊一個比較基礎的話題,就是實現登錄的方式有哪些?適合剛入行的朋友。

華山之 Session 絕學

Session 我們稱之為會話控制, 是一種在服務器端保持會話狀態的解決方案。通俗點來講就是客戶端訪問服務端的時候,會在服務端存儲對應的信息,生成一個 Session ID 返回給客戶端,客戶端下次過來的時候帶上 Session ID,這樣就能識別訪問者的身份。

請求中帶上 Session ID 最常見的方式就是通過 Cookie 來承載了,Cookie 是客戶端保存用戶信息的一種機制,在瀏覽器環境中,請求會自動帶上 Cookie 信息,服務端也就能獲取到 Session ID。

在後端實現登錄邏輯的時候,先獲取 HttpSession 對象,然後通過 setAttribute()來設置登錄的用戶信息,比如用戶 ID。驗證有沒有登錄的時候通過 getAttribute()來獲取對應的 Session 信息,如果沒有獲取到,則證明沒有登錄過或者會話失效了。

對於 Tomcat, Jetty 這些容器而言,Session 就是是一塊在服務器開闢的內存空間,存儲結構就是 Map。

Tomcat 的 Session 實現類是 StandardSession。

分佈式 Session 解決方案

如果你的應用是單節點部署,這種場景使用web容器實現的 Session 機制沒有問題。一但壓力過大,需要多節點部署的時候,Session 就需要進行分佈式的支持。

看下圖,當部署了兩個 Tomcat 的時候,通過 Nginx 進行負載均衡,第一次請求轉發到了 Tomcat1, Session 信息存儲在 Tomcat1 上面。第二次請求轉發到了 Tomcat2 上面,但是 Tomcat2 上面是沒有剛才的 Session 信息,這就是多節點下 Session 會出現的問題。

Session 複製

Tomcat 內置了 Session 複製的功能,也就是你的 Session 是在 Tomcat1 中產生的,Tomcat1 會將你的 Session 同步給 Tomcat2, 這樣當你的請求到了 Tomcat2 的時候,就能知道你的身份信息。

這種方案在其他的框架中也經常能見到,比如 Spring Cloud 體系中的 Eureka 註冊中心,也是採用複製的方式來同步註冊表的信息。

關於 Tomcat Session 複製相關配置請參考官方文檔://tomcat.apache.org/tomcat-8.0-doc/cluster-howto.html

黏性會話

黏性會話指的是對於同一個用戶的請求,永遠都只轉發到某一個 Tocmat 的實例上,這樣即使沒有做 Session 複製,也不會出現問題。如果有節點掛掉了就會訪問失敗。

常見的方式有對 IP 做 Hash 進行轉發,IP 不太可靠,因為會變。在 Nginx 中有一個 nginx-sticky-module 這個第三方模塊用於添加一個粘性 Cookie,該粘性 cookie 始終轉發到同一服務器。

nginx-sticky-module 會在 Cookie 中記錄一個值來標識當前請求需要被轉發到哪個節點,第一次沒有的時候會先轉發,然後在響應給客戶端之前寫入 Cookie。後面的請求都會在 Cookie 找到對應的標識,然後進行轉發到固定的節點。

Session 集中存儲

Session 複製會佔用服務器資源,影響性能。黏性會話存在單點故障風險。更好的分佈式 Session 方式就是集中式存儲。

所謂集中式存儲就是將會話信息統一存儲在某個地方,像 Tomcat 之類的 Web 服務器本身不存儲會話信息,這樣後端服務也就是無狀態的,方便隨時擴容。

至於實現方案的話有很多,大家可以自己去實現 HttpSession 做對應的存儲讀取邏輯,也可以採用開源的方案。比如 Spring Session 就是一個很好的開源方案,上手簡單,支持多種存儲方式,比如 Redis, Mysql 等。

如果對手寫 Spring Session 原理感興趣的,也可以參考我之前的這套課程://cxytiandi.com/course/5

少林之 Token 絕學

Token 認證是目前主流的認證方式之一,Token 最大的優勢在於無狀態,並且不用存儲會話信息。也就是說通過 Token 就可以知道當前訪問的用戶是誰,不需要去 Web 容器的內存中獲取,不需要去集中管理會話的存儲中去獲取。

Token 的生成方式有多種,可以自己定義固定的格式,比如裏面包含了用戶 ID,用戶名等信息。也可以使用目前主流的 JWT 方式。

JWT(JSON Web Token)是為了在網絡應用環境中傳遞聲明而執行的一種基於 JSON 的開放標準。JWT 的聲明一般被用在身份提供者和服務提供者間傳遞被認證的用戶身份信息, 以便從資源服務器獲取資源。

比如在用戶登錄時,基本思路就是用戶提供用戶名和密碼給認證服務器,服務器驗證用戶提交信息的合法性;如果驗證成功,會產生並返回一個 Token,後續請求用戶帶上這個 Token ,服務端就可以識別這個請求的身份信息。

JWT 由三部分構成,

  • 第一部分是頭部(Header);
  • 第二部分是消息體(Payload);
  • 第三部分是簽名(Signature)。

一個 JWT 生成的 Token 格式為:

token = encodeBase64(header) + ‘.’ + encodeBase64(payload) + ‘.’ + encodeBase64(signature)

頭部的信息通常由兩部分內容組成,令牌的類型和使用的簽名算法,比如下面的代碼:

{ “alg”: “HS256”, “typ”: “JWT” }

消息體中可以攜帶一些應用需要的信息,比如用戶 ID,代碼如下:

{ “id”: “1001”, “name”: “yinjihuan”}

簽名是用來判斷消息在傳遞的路徑上是否被篡改的,從而保證數據的安全性,格式如下:

HMACSHA256( base64UrlEncode(header)  + “.” +  base64UrlEncode(payload), secret)

通過這三部分就組成了我們的 JSON Web Token。

如何使用請參考 Github://github.com/jwtk/jjwt

如上圖所示:請求到達 Tomcat 後,可以調用單獨的 Token 服務進行 Token 的生成,也可以將 Token 的生成邏輯封裝成一個 jar 包來使用。需要注意的是如果用內嵌的方式,對應 Token 的加密配置要一致,否則會出現驗證失敗的情況。

Token 有點不好的地方在於無法主動讓它失效,比如我們用 Session 的場景,用戶退出登錄,直接將 Session 信息在服務端刪除即可,即使後面用相同的 Session 信息去請求,服務端也找不到對應的信息了。

Token 是一個加密的字符串,裏面包含了用戶的信息,加密算法,過期時間。如果過期時間設置的比較長,也就意味着在過期時間之前都可以使用。

如果要實現退出登錄的功能,既然不能對 Token 本身的過期時間進行改造,那麼可以使用一個黑名單的機制來進行過濾即可。將退出登錄的 Token 存儲起來,使用的地方去匹配是否註銷了,然後進行攔截即可。

關於作者:尹吉歡,簡單的技術愛好者,《Spring Cloud 微服務-全棧技術與案例解析》, 《Spring Cloud 微服務 入門 實戰與進階》作者, 公眾號猿天地發起人。

我整理了一份很全的學習資料,感興趣的可以微信搜索「猿天地」,回復關鍵字 「學習資料」獲取我整理好了的 Spring Cloud,Spring Cloud Alibaba,Sharding-JDBC 分庫分表,任務調度框架 XXL-JOB,MongoDB,爬蟲等相關資料。