登錄態 - SSO
1 背景
前一段時間,參與了老項目的遷移工作,配合後端接口遷移時,由於兩個項目採取了不一樣的登陸方案,所以遇到了跨域登錄態無法共享的問題。經過各方協調,最終老項目將遷移頁面部署在新項目的指定網關下,並且使用新項目的 SSO 登錄方案。由遷移中遇到的登陸態共享問題,引發了我對 SSO 的思考與學習。如果發現文章中有什麼錯誤之處,請及時指正~🙈
2 登錄態維護方式
2.1 JWT(Json Web Token)
2.1.1 是什麼?
JWT 是一個開放的 JSON 格式 token 存儲標準。它定義了一種安全、緊湊的方式來保存數據,通過簽名的方式來校驗 token 的合法性,主要支持的簽名算法如 HMAC、RSA、ECDSA。
通常使用它來保存登錄信息,相比傳統的 session 方案,它的優點在於服務端無需維護登錄態,不再需要依賴第三方存儲(如 redis、memcached),所以說 JWT 是無狀態的。
但它也存在缺點。由於它只在客戶端維護,因此服務端無法方便的清除登錄態,相比傳統的 session 方案,只需要將 session 清除即可。你可能會說,可以直接將這個 token 刪除就算退出登錄了。但實際上這只是一種假註銷,若該用戶再次拿到相同的 token 還是會被認爲是登錄的。
2.1.2 數據結構
實際上 JWT 是由header(頭部)
、payload(負載)
、signature(簽名)
這三個部分組成的,中間用.
來分隔開,寫成一行就是這個樣子的:Header.Payload.Signature
。
- Header: 該部分是一個 JSON 對象,描述 JWT 的元數據,通常是下面的樣子。
{
"alg": "HS256", // 表示簽名的算法(algorithm),默認是 HMAC SHA256(寫成 HS256)
"typ": "JWT" // 表示這個令牌(token)的類型(type),JWT 令牌默認統一寫爲 JWT
}
- Playload:該部分也是一個 JSON 對象,用來存放實際需要傳遞的數據。JWT 規定了 7 個官方字段,供選用。除了官方的字段,還可以自定義一些其他字段。
iss (issuer):簽發人
exp (expiration time):過期時間
sub (subject):主題
aud (audience):受衆
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號
⚠️ JWT 默認是不加密的,任何人都可以讀到,所以不要把祕密信息放在這個部分。這個 JSON 對象也要使用 Base64URL 算法轉成字符串。
- Signature :該部分是對前兩部分的簽名,防止數據篡改。首先,需要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。然後,使用 Header 裏面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。服務器收到 JWT 後通過對比簽名來確定 Token 是否被修改。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
- Base64Url:這個算法跟 Base64 算法基本類似,但有一些小的不同。JWT 作爲一個令牌,有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符
+
、/
和=
,在 URL 裏面有特殊含義,所以要被替換掉:=
被省略、+
替換成-
,/
替換成_
。這就是 Base64URL 算法。
2.1.3 特點
-
JWT 默認是不加密,但也是可以加密的。JWT 不加密的情況下,不能將敏感數據寫入 JWT。但是,生成原始 Token 以後,可以用密鑰再加密一次。
-
JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。
-
JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。
-
JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。爲了減少盜用,JWT 的有效期應該設置得比較短,並且 JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。對於一些比較重要的權限,使用時應該再次對用戶進行認證。
2.2 Session & Cookie
2.2.1 是什麼?
(1) Cookie
Cookie 是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶併發送到服務器上。通常,它用於告知服務端兩個請求是否來自同一瀏覽器,如保持用戶的登錄狀態。Cookie 使基於無狀態的 HTTP 協議記錄穩定的狀態信息成爲了可能。
有兩種類型的 Cookie,一種是 Session Cookie(會話期 Cookie),一種是 Persistent Cookie(持久性 Cookie),如果 Cookie 不包含到期日期,則將其視爲會話 Cookie。會話 Cookie 存儲在內存中,永遠不會寫入磁盤,當瀏覽器關閉時,此後 Cookie 將永久丟失。如果 Cookie 包含有效期
,則將其視爲持久性 Cookie,到期後,Cookie 將從磁盤中刪除。
主要用途:
-
會話狀態管理(如用戶登錄狀態、購物車、遊戲分數或其它需要記錄的信息)
-
個性化設置(如用戶自定義設置、主題等)
-
瀏覽器行爲跟蹤(如跟蹤分析用戶行爲等)
(2)Session
Session 代表着服務器和客戶端一次會話的過程。Session 對象存儲特定用戶會話所需的屬性及配置信息。這樣,當用戶在應用程序的 Web 頁之間跳轉時,存儲在 Session 對象中的變量將不會丟失,而是在整個用戶會話中一直存在下去。
常見誤區:Session 不是關閉瀏覽器就消失了。對 Session 來說,除非程序通知服務器刪除一個 Session,否則服務器會在 Session 失效前一直保留。大多數情況下瀏覽器是不會在關閉網頁之前通知服務器的,所以服務器根本不知道瀏覽器已經關閉。之所以會有這種錯覺,是大部分 session 機制都使用會話 cookie 來保存 session id,而關閉瀏覽器後這個 session id 就消失了,再次連接服務器時也就無法找到原來的 session。如果服務器設置的 cookie 被保存在硬盤上,或者使用某種手段改寫瀏覽器發出的 HTTP 請求頭,把原來的 session id 發送給服務器,則再次打開瀏覽器仍然能夠打開原來的 session。
2.2.2 session 和 cookie 關係
(1)關係
服務器第一次接收到請求時,開闢了一塊 Session 空間(創建了 Session 對象),同時生成一個 session id ,並通過響應頭的Set-Cookie:JSESSIONID=XXXXXXX
命令,向客戶端發送要求設置 Cookie 的響應;客戶端收到響應後,在本機客戶端設置了一個JSESSIONID=XXXXXXX
的 Cookie 信息,該 Cookie 的過期時間爲瀏覽器會話結束。接下來客戶端每次向同一個網站發送請求時,請求頭都會帶上該 Cookie 信息(包含 sessionId ), 然後服務器通過讀取請求頭中的 Cookie 信息,獲取名稱爲 JSESSIONID 的值,得到此次請求的 session id。
(2)區別
-
安全性: 由於 Session 是存儲在服務器端的,Cookie 是存儲在客戶端的,所以 Cookie 被盜用的可能性相較於 Session 會更高一些。
-
存取值的類型不同:Cookie 只支持存字符串數據,想要設置其他類型的數據,需要將其轉換成字符串,Session 可以存任意數據類型。
-
存儲大小不同: 單個 Cookie 保存的數據不能超過 4K,Session 可存儲數據遠高於 Cookie,但是當訪問量過多,會佔用過多的服務器資源。
2.2.3 Session id 的攜帶方式
-
Cookie:保存 session id 的方式可以採用 cookie,這樣在交互過程中瀏覽器可以自動的按照規則把這個標識發送給服務器。
-
URL 重寫:由於 cookie 可以被人爲的禁用,必須有其它的機制以便在 cookie 被禁用時仍然能夠把 session id 傳遞迴服務器,經常採用的一種技術叫做 URL 重寫,就是把 session id 附加在 URL 路徑的後面,附加的方式也有兩種,一種是作爲 URL 路徑的附加信息,另一種是作爲 query 字符串附加在 URL 後面。網絡在整個交互過程中始終保持狀態,就必須在每個客戶端可能請求的路徑後面都包含這個 session id。
3 單點登錄 (SSO)
3.1 SSO 是什麼
SSO(Single sign-on)即單點登錄,一種對於許多相互關聯,但是又是各自獨立的軟件系統,提供訪問控制的方法。
單點登錄 (SSO) 發生在用戶登錄到一個應用程序,然後自動登錄到其他應用程序時,無論用戶使用何種平臺、技術或域。例如,如果你登錄 Gmail 等 Google 服務,會自動通過 YouTube、AdSense、Google Analytics 和其他 Google 應用程序的身份驗證。同樣,如果退出 Gmail 或其他 Google 應用程序,將自動退出所有應用程序;這稱爲單點註銷 (SLO)。
3.2 CAS
3.2.1 是什麼?
CAS(Central Authentication Service), 集中式認證服務, 是 Yale 大學發起的一個企業級的、開源的項目,旨在爲 Web 應用系統提供一種可靠的單點登錄解決方法 (屬於 Web SSO)。下圖來自維基百科。
SSO 體系中的角色:
-
User:使用平臺的用戶
-
Web 應用:接入 CAS SSO 服務的平臺
-
SSO 認證中心 (cas service):提供 CAS SSO 服務的平臺
3.2.2 易混淆概念
- TGT:Ticket Grangting Ticket
TGT 是 CAS 爲用戶簽發的登錄票據,擁有了 TGT,用戶就可以證明自己在 CAS 成功登錄過。TGT 封裝了 Cookie 值以及此 Cookie 值對應的用戶信息。當 HTTP 請求到來時,CAS 以此 Cookie 值(TGC)爲 key 查詢緩存中有無 TGT ,如果有的話,則相信用戶已登錄過。
- TGC:Ticket Granting Cookie
CAS Server 生成 TGT 放入自己的 Session 中,而 TGC 就是這個 Session 的唯一標識(SessionId),以 Cookie 形式放到瀏覽器端,是 CAS Server 用來明確用戶身份的憑證。
- ST :Service Ticket
ST 是 CAS 爲用戶簽發的訪問某一 service 的票據。用戶訪問 service 時,service 發現用戶沒有 ST,則要求用戶去 CAS 獲取 ST。用戶向 CAS 發出獲取 ST 的請求,CAS 發現用戶有 TGT,則簽發一個 ST,返回給用戶。用戶拿着 ST 去訪問 service,service 拿 ST 去 CAS 驗證,驗證通過後,允許用戶訪問資源。ST 只能使用一次就會失效,這與 OAuth 中的 access_token 不同。
👇PGTIOU, PGT, PT 是 CAS 2.0 的 代理模式 中的內容 ,感興趣的同學可以自行了解。
- PGT:Proxy Granting Ticket
Proxy Service 的代理憑據。用戶通過 CAS 生成一個 PGT 對象,緩存在 PGTIOU。
- PGTIOU:Proxy Granting Ticket I Owe You
是 CAS 的 serviceValidate 接口驗證 ST 成功後,CAS 會生成驗證 ST 成功的 xml 消息,返回給 Proxy Service,xml 消息中含有 PGTIOU,proxy service 收到 Xml 消息後,會從中解析出 PGTIOU 的值,然後以其爲 key,在 map 中找出 PGT 的值,賦值給代表用戶信息的 Assertion 對象的 pgtId,同時在 map 中將其刪除。
- PT :Proxy Ticket
是用戶訪問 Target Service(back-end service)的票據。如果用戶訪問的是一個 Web 應用,則 Web 應用會要求瀏覽器提供 ST,瀏覽器就會用 cookie 去 CAS 獲取 ST,而是通過訪問 proxy service 的接口,憑藉 proxy service 的 PGT 去獲取一個 PT,然後才能訪問到此應用。
3.2.3 單點登錄 (SSO) & 單點登出 (SLO)
3.2.3.1 登陸流程
下圖是 CAS 官網的登錄時序圖,可以更好地幫助我們理解,建議細看一下~
3.2.3.2 單點登出
CAS 除了提供 SSO 功能,還提供了 SLO(單點登出)功能,由於 CAS Service 和 Client Service 各維護了一個登陸態,所以兩者之間的登錄態是割裂的,那我們應該怎麼實現 SLO 呢?
由於官方沒有給出一個詳細的流程圖,所以我就根據自己的理解畫了一個,供大家參考一下~
主要流程:
-
在某一個平臺請求退出登錄後,先在 query 攜帶 service 字段重定向到 CAS 退出登錄的頁面;
-
攜帶 service query 字段和 TGC 發送請求到 CAS Server;
-
CAS Server 通過 TGC 查詢到登錄信息,然後遍歷請求各個接入平臺的
/logout
接口; -
當所有業務的登錄態都清除後,就成功單點登出了。
3.3 OAUTH
3.3.1 是什麼?
在 OAuth 中 “O” 是 Open 的簡稱,表示 “開放” 的意思。Auth 表示 “授權” 的意思,所以連起來 OAuth 表示 “開放授權” 的意思,它是一個關於授權(authorization)的開放網絡標準。OAuth 允許用戶授權第三方應用訪問他存儲在另外服務商裏的各種信息數據,而這種授權不需要提供用戶名和密碼提供給第三方應用。比較直接的例子就是第三方 App 使用微信或 QQ 來登錄,這些授權登錄採用的就是 OAuth。
3.3.2 名詞定義
-
Third-party application:第三方應用客戶端,就相當於當前需要授權的 App
-
HTTP service: 服務提供商,指的是微信、QQ
-
Resource Owner:用戶 / 資源擁有者,本文指的是在微信中註冊的用戶
-
Authorization server: 認證服務器,在資源擁有者授權後,向客戶端授權 (頒發 access token) 的服務器
-
Resource server: 資源服務器,服務提供商存放用戶生成的資源的服務器。它與認證服務器,可以是同一臺服務器,也可以是不同的服務器。
3.3.3 四種授權模式
本部分只展示一下相關的時序圖,就不做文檔的搬運工了🐶,想了解更多的同學,可以看最後的推薦閱讀部分~
- 授權碼模式
這種方式是最常用的流程,安全性也最高,它適用於那些有後端的 Web 應用。授權碼通過前端傳送,令牌則是儲存在後端,而且所有與資源服務器的通信都在後端完成。這樣的前後端分離,可以避免令牌泄漏。
- 簡化模式
第四步,直接返回 access_token 時,有被劫持的風險,所以 OAuth 採用如下的方式傳遞 token
https://a.com/callback#token=ACCESS_TOKEN
上面這個 URL,token
參數就是令牌,客戶端在前端拿到令牌。注意,令牌的位置是 URL 錨點(fragment),而不是查詢字符串(querystring),這是因爲 OAuth 允許跳轉網址是 HTTP 協議,因此存在 "中間人攻擊" 的風險,而瀏覽器跳轉時,錨點不會發到服務器,就減少了泄漏令牌的風險。
這種方式把令牌直接傳給前端,是很不安全的。因此,只能用於一些安全要求不高的場景,並且令牌的有效期必須非常短,通常就是會話期間(session)有效,瀏覽器關掉,令牌就失效了。
- 密碼模式
採用這種方式不需要跳轉,而是把令牌放在 JSON 數據裏面,作爲 HTTP 響應,客戶端拿到令牌。這種方式需要用戶給出自己的用戶名 / 密碼,顯然風險很大,因此只適用於其他授權方式都無法採用的情況,而且必須是用戶高度信任的應用。
- 憑證模式、客戶端模式(client credentials)
適用於沒有前端的命令行應用,即在命令行下請求令牌。這種方式給出的令牌,是針對第三方應用的,而不是針對用戶的,即有可能多個用戶共享同一個令牌。
3.3.4 令牌的種類與更新
每個發到 API 的請求,都必須帶有令牌。具體做法是在請求的頭信息,加上一個
Authorization
字段,令牌就放在這個字段裏面。
服務提供商平臺頒發令牌的時候,一次性頒發兩個令牌,一個用於獲取數據的 access_token,另一個用於獲取新的令牌 refresh_token。access_token 的過期時間較短,refresh_token 的過期時間較長,當 access_token 過期了,就會使用 refresh_token 來請求新的 access_token。
通過 refresh_token 請求 access_token 的 url 示例:
https://server.example.com/oauth/token?
grant_type``=refresh_token&
client_id``=CLIENT_ID&
client_secret``=CLIENT_SECRET&
refresh_token``=REFRESH_TOKEN
參數解釋:
-
grant_type: 參數爲 refresh_token 表示要求更新令牌;
-
client_id:客戶端 id,第三方應用在服務提供者平臺註冊的,用於身份認證;
-
client_secret:授權服務器的祕鑰,第三方應用在服務提供者平臺註冊的,用於身份認證;
-
refresh_token: 參數就是用於更新令牌的令牌
3.4 CAS 對比 OAUTH
- 設計初衷
- CAS 專門爲中心化鑑權(authentication)而生,即 SSO,並且 3.5 版本後也支持通過 OAuth 協議進行登錄鑑權
-
OAuth 是用來處理授權(authorization)而生,實現 SSO 並不是它的初衷,它只關注如何讓第三方通過讓用戶無需登錄的方式獲得私有資源
-
只不過它也定義了完整的鑑權流程,如果你把用戶信息當做私有資源,你也可以使用它定義的規範來實現 SSO,但相比 CAS,它是沒有 SLO (single logout) 功能的。例如 github、微信 的第三方登錄都是基於 OAuth 實現
- 安全性
-
CAS
-
Service ticket 只能使用一次,並且有效期越短越好,而 OAuth 的 access_token 是有有效期的,有效期內可以使用多次
-
ticket 生成需要足夠隨機,如果被攻擊者猜出規律,則可以計算出下一個 ticket 值
-
OAuth
-
通過 state 參數 (放在 query 中) 可以有效防止 csrf 攻擊
-
對於 web 應用,access_token 通過後端方式接口獲取和接口使用,secret 不會展示在前端,暴露可能性低
-
對於前端應用,可以通過 PKCE extension 有效防止授權碼攔截攻擊
-
提供了 scope 機制來限制獲取到的資源範圍
- 資源存儲
-
CAS:Client 端 (應用系統) 是資源存儲端,用戶是資源消費者(應用使用者)。
-
OAuth2:Client 端 (第三方服務) 是資源消費端,通過用戶授權,允許用戶不提供自己賬號密碼的情況下,使 Client 端有權訪問用戶資源(如個人信息、通訊錄等)。
-
簡單來說:需要統一的賬號密碼進行身份認證,用 CAS;需要授權第三方服務使用我方資源,使用 OAuth。
4 實現一個簡單的 SSO
流程圖
首次登陸
非首次登陸
退出登錄
模塊
接口
用戶平臺接口
SSO 平臺接口
頁面
其他實現方案
前後端框架
本 demo 採用 eden monorepo 來組織共六個子項目,分別爲三個 React 前端子項目和三個 Node 後端子項目,後端採用的是公司內部封裝的 gulu 框架。
Token 持久化
由於 Node 後端需要記錄當前已登陸系統的用戶 token,所以本 Demo 採用 JSON 文件來暫時存儲已登錄用戶的 Token,使用 fs 來對 json 文件進行讀寫操作,模擬存儲和刪除 token 的過程。
本地端口
本次 demo 僅在本地運行展示,所有使用的均爲本地 localhost[1] 端口,端口對應如下
-
FE - 統一登錄平臺:3000
-
FE - 統一登錄平臺: 4000
-
FE - 平臺 A: 3001
-
BE - 平臺 A: 4001
-
FE - 平臺 B: 3002
-
BE - 平臺 B: 4002
前後端分離跨域問題
由於只是 demo 演示項目,所以採用的是 config 文件中的代理來暫時解決。
devServer: {
proxy: {
'/api': {
target: 'http://localhost:400x',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
},
},
5 問題討論
Q :JWT 如何註銷登錄?
A :由於 JWT 是無狀態的,服務端不存儲它,目前爲止還沒有了解到有什麼能夠不涉及到服務端存儲的註銷方式。
Q :業務方接入 SSO 系統,但是沒有實現維護登錄態的服務,會出現什麼問題?
A :SSO 系統,本質上仍然是 “授權” 服務, 即提供了集中式的授權管理, 但是 “鑑權” 應當業務方自己實現。例如:server 通過 token 獲取到了用戶的授權信息(user_id 之類的),如果業務方不把這個 “授權信息(登錄態)” 維護起來(session/JWT),那麼每次訪問都需要再走到 SSO Server,走一次完整流程。那麼就會導致以下兩個問題:1、對 SSO server 而言流量、請求會被放大;2、對用戶而言流程變長、響應時間變慢。
6 補充內容
6.1 加密算法
有興趣的可以看一下這篇文章:淺談常見的七種加密算法及實現 [2]
加密算法分對稱加密和非對稱加密,其中對稱加密算法的加密與解密密鑰相同,非對稱加密算法的加密密鑰與解密密鑰不同,此外,還有一類不需要密鑰的散列算法。常見的對稱加密算法主要有 DES
、3DES
、AES
等,常見的 非對稱算法 主要有RSA
、DSA
等,散列算法 主要有SHA-1
、MD5
等。
哈希算法的特點:
-
正像快速:原始數據可以快速計算出哈希值;
-
逆向困難:通過哈希值基本不可能推導出原始數據;
-
輸入敏感:原始數據只要有一點變動,得到的哈希值差別很大;
-
衝突避免:很難找到不同的原始數據得到相同的哈希值,宇宙中原子數大約在 10 的 60 次方到 80 次方之間,所以 2 的 256 次方有足夠的空間容納所有的可能;
參考資料
[1] localhost: http://localhost
[2] 淺談常見的七種加密算法及實現: https://juejin.cn/post/6844903638117122056
[3] 傻傻分不清之 Cookie、Session、Token、JWT: https://juejin.cn/post/6844904034181070861#heading-17
[4] 前端需要了解的 SSO 與 CAS 知識: https://juejin.cn/post/6844903509272297480
[5] OAuth.0 原理淺析: https://juejin.cn/post/7010636081305485319
[6] 理解 OAuth 2.0: https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
[7] OAuth 2.0 的四種方式: https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/_B_4YbKEsjNUd_hGDGHFAA