優化實戰 第 25 期 - 基於Token前端實現SSO單點登入

語言: CN / TW / HK

SSO.jpg

單點登入 (Single Sign On),簡稱為 SSO,是指在同一賬號平臺下的多個應用系統中,使用者只需要登入一次就可以訪問所有相互信任的應用系統

舉個例子,系統A系統B 都屬於某公司下的兩個不同的應用系統,當用戶登入 系統A 後,再開啟 系統B ,系統便會自動幫使用者登入 系統B ,這種現象就屬於單點登入

JWT鑑權原理

cc420cac591448b1b6a279efa5c5d52e.png

1、客戶端使用賬號和密碼請求登入

2、服務端收到請求,去驗證賬號與密碼

3、驗證通過後,服務端會簽發一個令牌 Token 並把這個令牌傳送給客戶端

4、客戶端收到令牌將其儲存到本地 localStorage

5、客戶端每次向服務端請求資源時都需要在 Header Authorization 中攜帶令牌,允許使用者訪問該令牌允許的路由、服務和資源

6、服務端收到請求後進行解密並通過祕鑰驗證令牌,驗證通過返回資源,不通過則返回 401 狀態碼

JWT的介紹

fc1f4134970a304ea501a2f40d06848fc8175cf0.png

JSON Web Token ,簡稱 JWT ,一種基於 JSON 的認證授權機制,是一個非常輕巧的標準規範。這個規範允許我們在使用者和伺服器之間傳遞安全可靠的資訊

JWT 也是目前最流行的跨域認證解決方案

JWT的構成

  • 頭部(Header) json { "alg": "HS256", "typ": "jwt" } 通常由令牌的型別 typ 和所使用的簽名演算法 alg 組成,並使用 Base64URL 進行編碼組成 JWT 結構的第一部分

  • 載荷(Payload) json { "exp": Math.floor(Date.now() / 1000 + 60 * 60), // 設定有效期為 1 個小時 "uid": "188888" } 除了設定過期時間 exp 和使用者的 uid ,還可以新增自定義私有欄位,使用 Base64URL 進行編碼組成 JWT 結構的第二部分

  • 簽名(Signature)

71cf3bc79f3df8dcf8062d8f12df5182461028c8.png

js HMACSHA256(base64UrlEncode(Header) + "." + base64UrlEncode(payload), secret) 在服務端設定一個私鑰 secret ,使用 Header 指定的演算法 HS256 對頭部 Header 和 載荷 Payload 進行加密生成簽名。

  • 生成 Token

HeaderPayloadSignature 拼成一個用 英文句號 分隔的字串,即為 Token

jwt_token.webp

  • 進行驗籤

服務端利用簽名可以校驗 Token 是否被篡改

2934349b033b5bb55554ae46ea1df630b400bcdb.png

  • 注意事項

預設 JWT 是不加密的,所以不要把重要資訊放在載荷 Payload 部分

儲存在服務端的 secret 是用來進行 JWT 的簽發和驗證的,所以在任何場景都不應該洩漏出去。如果客戶端得知了這個 secret ,那就意味著客戶端可以對 JWT 進行自我簽發

JWT的特點

  • 無狀態化

由於伺服器或 Session 中不會儲存任何使用者資訊,所以基於 Token 的使用者認證是一種伺服器無狀態的認證方式

沒有會話資訊意味著應用程式可以根據需要擴充套件和新增更多的機器(伺服器叢集),而不需要考慮使用者是在哪一臺伺服器登入的,適用於 分散式系統

  • 易於擴充套件

由於 JWT 儲存在應用系統,服務端不進行會話儲存,所以便於服務端叢集水平擴充套件

可以在 JWT 中的載荷 Payload 部分新增自定義的內容用於業務需要

  • 支援跨域

由於 Token 儲存於應用系統,完全由應用系統管理,既減輕了服務端的記憶體壓力也避開了 同源策略 的限制

這樣就可以給任何域名提供 API 服務,不需要擔心跨域資源共享問題,即 CORS

  • 防止 CSRF 攻擊

前端向服務端傳送請求時,將本地儲存的 Token 手動新增到 Authorization 的請求頭欄位中,這樣就避免了 CSRF 漏洞攻擊

共享登入狀態

  • 實現方案

前端拿到 Token 後,不僅要將它寫入當前域下的 localStorage 中,還要通過 iframe + postMessage() 的方式將它寫入多個信任的其他域下的 localStorage 中,從而實現登入狀態的共享

iframe_postMessage.jpeg

  • 實現程式碼

Token 寫入多個域名下的 localStoragejs const iframe = document.createElement('iframe') iframe.src = 'http://www.app.com/static/bridge.html' iframe.addEventListener('load', event => { iframe.contentWindow.postMessage(token, 'http://www.app.com/static/bridge.html') }) document.body.append(iframe)

iframe 載入的頁面中繫結事件監聽器,用來接收 Token 資料 js window.addEventListener('message', ({ data, origin, srouce }) => { localStorage.setItem('AUTH-TOKEN', data) })

在各個應用系統請求的 Header 中攜帶 Token 令牌 js config.headers.common['Authorization'] = 'Bearer ' + token 說明:Bearer 是 JWT 的認證頭部資訊

  • 總結

這種實現方式完全由前端控制,幾乎不需要後端參與且支援跨域;出於安全因素考慮應始終使用 originsource 屬性驗證發信息人的身份

Token 的安全性

  • 安全概覽

為了減少 JWT Token 的洩漏風險,其有效期應該設定得短一些並採用 HTTPS 協議。這樣就會存在 JWT Token 過期的情況,導致使用者頻繁的去登入獲取新的 JWT Token,嚴重影響使用者體驗

  • 解決方案

生成 JWT Token 的同時生成 refresh_token,其中 refresh_token 的有效時間長於 JWT Token,當 JWT Token 過期之後,使用 refresh_token 獲取新的 JWT Token 與 refresh_token,這樣使用者就可以享受無感知的重新整理體驗

每個 Refresh Token 只能使用一次

一起學習,加群交流看 沸點