優化實戰 第 25 期 - 基於Token前端實現SSO單點登入
單點登入 (Single Sign On),簡稱為 SSO
,是指在同一賬號平臺下的多個應用系統中,使用者只需要登入一次就可以訪問所有相互信任的應用系統
舉個例子,系統A
和 系統B
都屬於某公司下的兩個不同的應用系統,當用戶登入 系統A
後,再開啟 系統B
,系統便會自動幫使用者登入 系統B
,這種現象就屬於單點登入
JWT鑑權原理
1、客戶端使用賬號和密碼請求登入
2、服務端收到請求,去驗證賬號與密碼
3、驗證通過後,服務端會簽發一個令牌 Token
並把這個令牌傳送給客戶端
4、客戶端收到令牌將其儲存到本地 localStorage
中
5、客戶端每次向服務端請求資源時都需要在 Header Authorization
中攜帶令牌,允許使用者訪問該令牌允許的路由、服務和資源
6、服務端收到請求後進行解密並通過祕鑰驗證令牌,驗證通過返回資源,不通過則返回 401
狀態碼
JWT的介紹
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)
js
HMACSHA256(base64UrlEncode(Header) + "." + base64UrlEncode(payload), secret)
在服務端設定一個私鑰 secret
,使用 Header
指定的演算法 HS256
對頭部 Header
和 載荷 Payload
進行加密生成簽名。
- 生成 Token
把 Header
、Payload
、Signature
拼成一個用 英文句號
分隔的字串,即為 Token
- 進行驗籤
服務端利用簽名可以校驗 Token
是否被篡改
- 注意事項
預設 JWT
是不加密的,所以不要把重要資訊放在載荷 Payload
部分
儲存在服務端的 secret
是用來進行 JWT
的簽發和驗證的,所以在任何場景都不應該洩漏出去。如果客戶端得知了這個 secret
,那就意味著客戶端可以對 JWT
進行自我簽發
JWT的特點
- 無狀態化
由於伺服器或 Session
中不會儲存任何使用者資訊,所以基於 Token
的使用者認證是一種伺服器無狀態的認證方式
沒有會話資訊意味著應用程式可以根據需要擴充套件和新增更多的機器(伺服器叢集),而不需要考慮使用者是在哪一臺伺服器登入的,適用於 分散式系統
- 易於擴充套件
由於 JWT
儲存在應用系統,服務端不進行會話儲存,所以便於服務端叢集水平擴充套件
可以在 JWT
中的載荷 Payload
部分新增自定義的內容用於業務需要
- 支援跨域
由於 Token
儲存於應用系統,完全由應用系統管理,既減輕了服務端的記憶體壓力也避開了 同源策略
的限制
這樣就可以給任何域名提供 API
服務,不需要擔心跨域資源共享問題,即 CORS
- 防止 CSRF 攻擊
前端向服務端傳送請求時,將本地儲存的 Token
手動新增到 Authorization
的請求頭欄位中,這樣就避免了 CSRF
漏洞攻擊
共享登入狀態
- 實現方案
前端拿到 Token
後,不僅要將它寫入當前域下的 localStorage
中,還要通過 iframe + postMessage()
的方式將它寫入多個信任的其他域下的 localStorage
中,從而實現登入狀態的共享
- 實現程式碼
將 Token
寫入多個域名下的 localStorage
中
js
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 的認證頭部資訊
- 總結
這種實現方式完全由前端控制,幾乎不需要後端參與且支援跨域;出於安全因素考慮應始終使用 origin
和 source
屬性驗證發信息人的身份
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 只能使用一次
一起學習,加群交流看 沸點