【原創申明:文章為原創,歡迎非盈利性轉載,但轉載必須注明來源】
單點登錄的解決方案有很多,各個解決方案有自己的特點。本文不是為了介紹某一種單點登錄方案,只是介紹單點登錄的思路,以及必要的技術基礎。
一、網站登錄是怎么回事
在一個普通的網站開發中,Web Server怎么知道當前用戶是誰?
1. 典型WEB
在典型的WEB應用中,應用大致包含三類數據:用戶數據、權限數據、業務數據。用戶打開瀏覽器后,首先跳轉到登錄界面,登錄成功后就可以繼續訪問。下圖是一個簡單的描述。
問題來了,在登錄成功后的后續訪問中,服務器是怎么知道當前請求的用戶到底是誰呢?我們都知道,HTTP是無連接的協議,每次瀏覽器的請求,對服務器來說都是一個新的請求,它怎么知道是上次登錄的那個瀏覽器上發來的?
這就要提到兩個概念:Cookie和Session。如果完全不懂這幾個概念,自行找點編程書看看吧,還不太適合看這篇文章。
2. Cookie和Session的關系
在瀏覽器中,會維護一個Cookie,JavaWeb中這個Cookie名叫jsessionid。在Web Server中,會維護一個SessionMap,將一個jsessionid同一個Session對象進行綁定。這樣,當瀏覽器訪問時的jsessionid這個Cookie送到服務器后,服務器就能得到所對應的Session對象。
3. Cookie和Session的綁定過程
Cookie和Session的綁定過程如下
① 用戶打開瀏覽器后第一次訪問網站
用戶第一次訪問,服務器會創建一個新的Session對象和Cookie,實現二者的綁定,並將Cookie寫入到用戶瀏覽器中。
② 瀏覽器中后續訪問時
用戶后續訪問時,瀏覽器會讀出Cookie隨同Request提交到服務器上,服務器根據Cookie,查找到當前瀏覽器對應的Session信息。
③ 用戶登錄成功時
用戶登錄成功后,服務器會在當前用戶的Session中寫入用戶信息。
下次用戶訪問時,服務器根據Cookie得到用戶的Session,就可以從中讀出當前用戶的信息。
4. WEB服務器集群部署會有什么問題
將WEB服務器部署為集群,如果不做任何設置,用戶會話可能會出現丟失。
當用戶在Server1中登錄后,在Server1中創建了Session。如果下次用戶被分發到Server2,這時候會無法得到Session信息,導致系統異常。
① 從前端分發處解決
讓同一個瀏覽器發送來的請求,永遠分發到同一台Server。
② Server的Session復制
③ Server共享Session
多個Server,可以配置使用一個共享的Session信息,比如使用Redis保存會話。
二、單點登錄需要解決哪些問題
整個應用環境,已經有很多套系統在運行,各自有自己的用戶管理、登錄管理。突然老板說要把所有的系統用戶整合在一起,實現單點登錄。怎么辦?
1. 用戶數據如何維護
原有的每個系統,都有自己的用戶管理,整合單點登錄后,怎么處理?大致會有以下一些處理方案:
① 各個系統中自行維護
這種方案最簡單,用戶管理部分不用做任何變化。但是需要做一些約定:相同的用戶應該使用同一個登錄名;用戶在一個系統中進行了維護,必須在其他系統中同時維護。
② 原有的一個主要系統中維護
原有的系統中,如果有一個側重於用戶信息管理的系統,可以將這個系統作為用戶維護的主系統,其他系統不再保留用戶管理功能,而改為從這個主系統中同步。
③ 獨立一個用戶管理系統
構建一套獨立的用戶管理系統,獨立於原有的各個系統。如,使用LDAP、Windows Exchange。原有的用戶系統都從這套系統中進行用戶同步。
2. 用戶數據同步的機制
前面采用了第二或第三種方案后,需要考慮用戶數據如何同步的問題。可以考慮的有幾種方案。為描述方便,將負責用戶管理的系統稱為主系統,其他系統成為子系統。
① 主系統主動推送
各系統實現同步信息變更接口。當用戶信息變更后,主系統主動向所有子系統推送用戶變更信息。這樣,能保證用戶信息得到實時的更新。
這種方案的缺點:1、更新時某個子系統正在存在故障,更新失敗,以后可能找不到更新的時機了;2、某個子系統部署位置特殊,不能被主系統訪問。
② 子系統定期發起同步請求
主系統實現同步接口,由子系統定期主動發起同步請求。主系統將某個時間段之后的所有變更,按照特定格式封裝后返回給子系統,由子系統負責解析並更新到本地用戶表。
③ 用戶登錄后訪問子系統時更新個人信息
用戶登錄后,跳轉到子系統時,從主系統中查詢當前用戶的信息,並同子系統進行比較,如有變更則更新本地用戶信息。
這種方案的缺點:1、如果用戶不訪問子系統,永遠沒有更新的機會;2、用戶被刪除或禁用,沒有機會更新子系統。
三種方案各有特點,可根據情況選擇,或者同時用多種方案配合實現。
3. 用戶權限怎么實現
用戶信息統一管理后,一個隨之而來的問題,就是權限信息如何管理?簡單分析,大致有三種解決方案:
① 各系統自行實現權限定義和判斷
這個方案最簡單,對原有系統不做任何修改。
這個方案有一個缺點:各個子系統的超級管理員如何設置。原來可能是將admin或root作為超管,可能各個子系統對超管賬號設置不同,就會碰到定義困難。
② 將權限統一管理
在用戶管理主系統中,同時定義角色、權限等信息,在主系統中實現用戶權限設置,並在用戶跳轉到子系統時,能夠准確的讀取到用戶在本系統中的授權信息。
這個方案對更適用於新開發的系統,權限從頭開發。針對老系統改造,可能修改工作量比較大。
③ 主系統定義部分權限
將前面兩種方案整合,可以做一個新方案:由主系統定義子系統的部分權限,更多權限由各個子系統自行定義。
比如,主系統只需定義各個子系統的超級管理員。
4. 單點登錄的一些備選方案
前面分析了用戶和權限的改造,它們是為統一用戶做基礎准備工作。現在該討論核心問題:如何實現單點登錄?
列出一些能想到的解決方案。有些方案看似問題很大,但真有人這么做,因為:做起來簡單。
① 域內Cookie共享
這是利用Cookie的特點,它是按照域名存儲的。如果多個應用使用的是同一個域名,則這些應用都能讀取到同一個Cookie。
如圖,用戶在主系統中登錄后,創建一個cookie,域名設置為xyz.com。這時候,*.xyz.com的所有服務器,都能讀取到這個域名。如果不指定域名,缺省使用的是主系統的域名www.xyz.com,其他子系統就不能讀取同一個Cookie了。
子系統讀取到userId這個cookie后,知道這是登錄用戶的id,從數據庫中讀取對應用戶的信息並保存到Session中即可。
② 傳登陸用戶的userId明文
用戶在主系統中登錄后,在主系統中提供子系統鏈接,並將用戶說的唯一標識通過參數傳遞給子系統。
子系統讀取到這個userId后,知道這是登錄用戶的id,從數據庫中讀取對應用戶的信息並保存到Session中即可。
③ 傳登陸用戶的userId密文
如果覺得傳輸用戶的userId明文不夠安全,可以考慮將參數值加密進行傳輸。
子系統接收到userId參數的密文后,首先解密,知道這是登錄用戶的id,從數據庫中讀取對應用戶的信息並保存到Session中即可。
④ 傳遞token替代userId
前面兩種方案,通過鏈接傳輸用戶ID,都不夠安全。一旦黑客拿到了某個用戶的userId或加密后的userId密文,他就能欺騙子系統。即使他並沒有登錄主系統,只需要傳遞同一個參數值,子系統就會認為該用戶已經登陸。
因此可以設計出更安全的單點登錄方案,用戶訪問子系統時,傳遞一個臨時token,而不是真實的userId。
如果用戶在主系統以用戶 admin 登錄,在主系統中生成一個token,保存在session中,並在所有的子系統鏈接中增加token參數。實際登錄子系統過程變成如下:
1) 瀏覽器訪問子系統1: web1.xyz.com?token=t12
2) 子系統拿到token=t12,不認識,它去問主系統:t12是誰?
3) 主系統拿到t12后,在自己維護的會話表中檢查了一下,告訴子系統1:這是 admin。
這樣,用戶就完成了登錄子系統1的過程。
⑤ 使用一次性token
前面這個方案,整個會話周期,都是一個token,也不夠安全。如果黑客拿到了訪問第一個子系統的token,他就能在自己的瀏覽器中利用這個token訪問第二個子系統,順利完成登錄。
這個方案針對方案4做改進,每次構造子系統鏈接時,生成一個新的token,當子系統驗證用戶成功后,立刻拋棄這個token,后續用戶就不能再使用這個token進行驗證。
這個方案還有兩個明顯的缺點
1) token的生命周期很長,黑客有可能搶在用戶登陸子系統之前用token去驗證,這樣黑客反而搶在了真實用戶的前面登錄,真實用戶進不去了。
2) 需要提前在主系統中為子系統創建鏈接並添加token參數,不夠靈活。
⑥ 使用更成熟的單點登錄解決方案
前三種方案都是比較簡單的處理方法,不夠安全。更安全合理的做法,是基於第三方成熟的解決方案進行定制,或者是參考這些方案,設計更符合環境特點的單點登錄方案。
成熟的方案,大致有:CAS、OAuth1.0/2.0、OpenID,以及QQ等第三方提供的單點登錄接入方案。
其中,CAS是一個非常成熟的解決方案,並且有Java、PHP等各種開發語言的子系統集成方案,網絡上也有比較豐富的開發文檔。當需要提供單點登錄方案時,可以有優先考慮它,並進行必要的定制。