Java的登入功能
一、單系統登入機制
1、http無狀態協議
web應用採用browser/server架構,http作為通訊協議。http是無狀態協議,瀏覽器的每一次請求,伺服器會獨立處理,不與之前或之後的請求產生關聯,這個過程用下圖說明,三次請求/響應對之間沒有任何聯絡
但這也同時意味著,任何使用者都能通過瀏覽器訪問伺服器資源,如果想保護伺服器的某些資源,必須限制瀏覽器請求;要限制瀏覽器請求,必須鑑別瀏覽器請求,響應合法請求,忽略非法請求;要鑑別瀏覽器請求,必須清楚瀏覽器請求狀態。既然http協議無狀態,那就讓伺服器和瀏覽器共同維護一個狀態吧!這就是會話機制
2、會話機制
瀏覽器第一次請求伺服器,伺服器建立一個會話,並將會話的id作為響應的一部分發送給瀏覽器,瀏覽器儲存會話id,並在後續第二次和第三次請求中帶上會話id,伺服器取得請求中的會話id就知道是不是同一個使用者了,這個過程用下圖說明,後續請求與第一次請求產生了關聯
伺服器在記憶體中儲存會話物件,瀏覽器怎麼儲存會話id呢?你可能會想到兩種方式
- 請求引數
- cookie
將會話id作為每一個請求的引數,伺服器接收請求自然能解析引數獲得會話id,並藉此判斷是否來自同一會話,很明顯,這種方式不靠譜。那就瀏覽器自己來維護這個會話id吧,每次傳送http請求時瀏覽器自動傳送會話id,cookie機制正好用來做這件事。cookie是瀏覽器用來儲存少量資料的一種機制,資料以”key/value“形式儲存,瀏覽器傳送http請求時自動附帶cookie資訊
tomcat會話機制當然也實現了cookie,訪問tomcat伺服器時,瀏覽器中可以看到一個名為“JSESSIONID”的cookie,這就是tomcat會話機制維護的會話id,使用了cookie的請求響應過程如下圖
3、登入狀態
有了會話機制,登入狀態就好明白了,我們假設瀏覽器第一次請求伺服器需要輸入使用者名稱與密碼驗證身份,伺服器拿到使用者名稱密碼去資料庫比對,正確的話說明當前持有這個會話的使用者是合法使用者,應該將這個會話標記為“已授權”或者“已登入”等等之類的狀態,既然是會話的狀態,自然要儲存在會話物件中,tomcat在會話物件中設定登入狀態如下
sessionid是一個會話的key,瀏覽器第一次訪問伺服器會在伺服器端生成一個session,有一個sessionid和它對應。tomcat生成的sessionid叫做jsessionid。
session在訪問tomcat伺服器HttpServletRequest的getSession(true)的時候建立,tomcat的ManagerBase類提供建立sessionid的方法:隨機數+時間+jvmid;
儲存在伺服器的記憶體中,tomcat的StandardManager類將session儲存在記憶體中,也可以持久化到file,資料庫,memcache,redis等。客戶端只儲存sessionid到cookie中,而不會儲存session,session銷燬只能通過invalidate或超時,關掉瀏覽器並不會關閉session。
那麼Session在何時建立呢?當然還是在伺服器端程式執行的過程中建立的,不同語言實現的應用程式有不同建立Session的方法,而在Java中是通過呼叫HttpServletRequest的getSession方法(使用true作為引數)建立的。在建立了Session的同時,伺服器會為該Session生成唯一的Session id,而這個Session id在隨後的請求中會被用來重新獲得已經建立的Session;在Session被建立之後,就可以呼叫Session相關的方法往Session中增加內容了,而這些內容只會儲存在伺服器中,發到客戶端的只有Session id;當客戶端再次傳送請求的時候,會將這個Session id帶上,伺服器接受到請求之後就會依據Session id找到相應的Session,從而再次使用之。
建立:sessionid第一次產生是在直到某server端程式呼叫 HttpServletRequest.getSession(true)這樣的語句時才被建立。
刪除:超時;程式呼叫HttpSession.invalidate();程式關閉;
session存放在哪裡:伺服器端的記憶體中。不過session可以通過特殊的方式做持久化管理(memcache,redis)。
session的id是從哪裡來的,sessionID是如何使用的:當客戶端第一次請求session物件時候,伺服器會為客戶端建立一個session,並將通過特殊演算法算出一個session的ID,用來標識該session物件
session會因為瀏覽器的關閉而刪除嗎?
不會,session只會通過上面提到的方式去關閉。
下面是tomcat中session的建立:
ManagerBase是所有session管理工具類的基類,它是一個抽象類,所有具體實現session管理功能的類都要繼承這個類,該類有一個受保護的方法,該方法就是建立sessionId值的方法:
(tomcat的session的id值生成的機制是一個隨機數加時間加上jvm的id值,jvm的id值會根據伺服器的硬體資訊計算得來,因此不同jvm的id值都是唯一的),
StandardManager類是tomcat容器裡預設的session管理實現類,
它會將session的資訊儲存到web容器所在伺服器的記憶體裡。
PersistentManagerBase也是繼承ManagerBase類,它是所有持久化儲存session資訊的基類,PersistentManager繼承了PersistentManagerBase,但是這個類只是多了一個靜態變數和一個getName方法,目前看來意義不大,對於持久化儲存session,tomcat還提供了StoreBase的抽象類,它是所有持久化儲存session的基類,另外tomcat還給出了檔案儲存FileStore和資料儲存JDBCStore兩個實現。
總結:session只需要通過HttpSession hs = req.getSession();這語句建立,而sessionId由伺服器按照一定的演算法自己建立,該語句表示獲取當前的session,如果當前不存在session則新建一個session。程式設計師可以自行通過setAttribute方法設定相應的屬性,還可以通過getAttribute方法獲取session的相關屬性。
樣例
在登陸的服務端:
程式碼:
//成功驗證使用者為該系統的合法使用者
//建立伺服器的session,獲取當前的sessioin,如果當前沒有session就新建立一個session
HttpSession hs = request.getSession(true);
//儲存當前的使用者的資訊
hs.setAttribute("loginUser", userReturn);
//設定使用者的狀態
hs.setAttribute("isLogin", true);
http的每次請求都必須進行伺服器的驗證,驗證該請求的cookie的sessionId所對應的session物件中的使用者登入狀態。這個一般使用過濾器完成(Filter)
程式碼:
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
//儲存當前的使用者的資訊
HttpSession hs = req.getSession();
//判斷使用者的登入狀態
if((Boolean) hs.getAttribute("isLogin")) {
res.sendRedirect("/OurBookingV1/index.jsp");
return;
}
實現了登入狀態的瀏覽器請求伺服器模型如下圖描述
每次請求受保護資源時都會檢查會話物件中的登入狀態,只有 isLogin=true 的會話才能訪問,登入機制因此而實現。
二、多系統的複雜性
web系統早已從久遠的單系統發展成為如今由多系統組成的應用群,面對如此眾多的系統,使用者難道要一個一個登入、然後一個一個登出嗎?就像下圖描述的這樣
web系統由單系統發展成多系統組成的應用群,複雜性應該由系統內部承擔,而不是使用者。無論web系統內部多麼複雜,對使用者而言,都是一個統一的整體,也就是說,使用者訪問web系統的整個應用群與訪問單個系統一樣,登入/登出只要一次就夠了
雖然單系統的登入解決方案很完美,但對於多系統應用群已經不再適用了,為什麼呢?
單系統登入解決方案的核心是cookie,cookie攜帶會話id在瀏覽器與伺服器之間維護會話狀態。但cookie是有限制的,這個限制就是cookie的域(通常對應網站的域名),瀏覽器傳送http請求時會自動攜帶與該域匹配的cookie,而不是所有cookie
既然這樣,為什麼不將web應用群中所有子系統的域名統一在一個頂級域名下,例如“*.baidu.com”,然後將它們的cookie域設定為“baidu.com”,這種做法理論上是可以的,甚至早期很多多系統登入就採用這種同域名共享cookie的方式。
然而,可行並不代表好,共享cookie的方式存在眾多侷限。首先,應用群域名得統一;其次,應用群各系統使用的技術(至少是web伺服器)要相同,不然cookie的key值(tomcat為JSESSIONID)不同,無法維持會話,共享cookie的方式是無法實現跨語言技術平臺登入的,比如java、php、.net系統之間;第三,cookie本身不安全。
因此,我們需要一種全新的登入方式來實現多系統應用群的登入,這就是單點登入
三、單點登入
什麼是單點登入?單點登入全稱Single Sign On(以下簡稱SSO),是指在多系統應用群中登入一個系統,便可在其他所有系統中得到授權而無需再次登入,包括單點登入與單點登出兩部分
1、登入
相比於單系統登入,sso需要一個獨立的認證中心,只有認證中心能接受使用者的使用者名稱密碼等安全資訊,其他系統不提供登入入口,只接受認證中心的間接授權。間接授權通過令牌實現,sso認證中心驗證使用者的使用者名稱密碼沒問題,建立授權令牌,在接下來的跳轉過程中,授權令牌作為引數傳送給各個子系統,子系統拿到令牌,即得到了授權,可以藉此建立區域性會話,區域性會話登入方式與單系統的登入方式相同。這個過程,也就是單點登入的原理,用下圖說明
下面對上圖簡要描述
- 使用者訪問系統1的受保護資源,系統1發現使用者未登入,跳轉至sso認證中心,並將自己的地址作為引數
- sso認證中心發現使用者未登入,將使用者引導至登入頁面
- 使用者輸入使用者名稱密碼提交登入申請
- sso認證中心校驗使用者資訊,建立使用者與sso認證中心之間的會話,稱為全域性會話,同時建立授權令牌
- sso認證中心帶著令牌跳轉會最初的請求地址(系統1)
- 系統1拿到令牌,去sso認證中心校驗令牌是否有效
- sso認證中心校驗令牌,返回有效,註冊系統1
- 系統1使用該令牌建立與使用者的會話,稱為區域性會話,返回受保護資源
- 使用者訪問系統2的受保護資源
- 系統2發現使用者未登入,跳轉至sso認證中心,並將自己的地址作為引數
- sso認證中心發現使用者已登入,跳轉回系統2的地址,並附上令牌
- 系統2拿到令牌,去sso認證中心校驗令牌是否有效
- sso認證中心校驗令牌,返回有效,註冊系統2
- 系統2使用該令牌建立與使用者的區域性會話,返回受保護資源
使用者登入成功之後,會與sso認證中心及各個子系統建立會話,使用者與sso認證中心建立的會話稱為全域性會話,使用者與各個子系統建立的會話稱為區域性會話,區域性會話建立之後,使用者訪問子系統受保護資源將不再通過sso認證中心,全域性會話與區域性會話有如下約束關係
- 區域性會話存在,全域性會話一定存在
- 全域性會話存在,區域性會話不一定存在
- 全域性會話銷燬,區域性會話必須銷燬
你可以通過部落格園、百度、csdn、淘寶等網站的登入過程加深對單點登入的理解,注意觀察登入過程中的跳轉url與引數
2、登出
單點登入自然也要單點登出,在一個子系統中登出,所有子系統的會話都將被銷燬,用下面的圖來說明
sso認證中心一直監聽全域性會話的狀態,一旦全域性會話銷燬,監聽器將通知所有註冊系統執行登出操作
下面對上圖簡要說明
- 使用者向系統1發起登出請求
- 系統1根據使用者與系統1建立的會話id拿到令牌,向sso認證中心發起登出請求
- sso認證中心校驗令牌有效,銷燬全域性會話,同時取出所有用此令牌註冊的系統地址
- sso認證中心向所有註冊系統發起登出請求
- 各註冊系統接收sso認證中心的登出請求,銷燬區域性會話
- sso認證中心引導使用者至登入頁面
四、部署圖
單點登入涉及sso認證中心與眾子系統,子系統與sso認證中心需要通訊以交換令牌、校驗令牌及發起登出請求,因而子系統必須整合sso的客戶端,sso認證中心則是sso服務端,整個單點登入過程實質是sso客戶端與服務端通訊的過程,用下圖描述
sso認證中心與sso客戶端通訊方式有多種,這裡以簡單好用的httpClient為例,web service、rpc、restful api都可以