1.超時問題
我們提供的CAS開源單點登錄SSO組件,它部署節點主要有2個:SSO服務器(部署內容爲一個web應用)、應用系統客戶端(部署內容爲cas客戶端casclient.jar包和相關配置文件)。因此我們根據SSO機制分析一下什麼情況下會出現超時。多個應用系統進行SSO集成後,SSO單點登錄過程中,登錄成功後,應用系統客戶端(以下用瀏覽器客戶端爲例)的session會保存認證後的用戶上下文,SSO服務器會生成一個用戶憑證票據(TGT)並緩存起來,瀏覽器客戶端會保存TGC(瀏覽器cookie中存儲的TGT),TGT是作爲發放SSO訪問服務的票據(ST)的一個憑證票據,發放ST票據後才能正常訪問。而瀏覽器客戶端的session會超時(如一般web應用客戶端可以設置session的timeout值爲30分鐘或更長),超時後會讓session失效,清空用戶上下文,TGC因爲仍然是保存在瀏覽器cookie中,只有關閉瀏覽器纔會清除。SSO服務器端的超時主要是TGT、ST超時,我們一般會設置超時值TGT爲2小時,ST爲5分鐘。關於ST票據使用,一般在首次SSO訪問服務時攜帶着該票據參數,驗證票據後能正常訪問後,SSO服務器就將此ST銷燬失效了;關於TGT票據的使用,一般是正常訪問時一直保持爲超時時間(2小時),除非做單點退出會銷燬TGT。基於以上分析,我們可以得出結論,SSO的超時主要涉及2個要素:瀏覽器的session超時值、TGT的超時值。一般系統設置TGT的超時值>瀏覽器的session超時值,那麼可能有2種超時情況:1.TGT超時(瀏覽器session超時值小,自然也超時);2.瀏覽器session超時,TGT不超時。
第一種“1.TGT超時”,這個處理很簡單,用戶的有效憑證票據都失效了,自然要重新取得有效憑證票據TGT,需要做的就是重新跳轉到登錄頁面重新登錄。
第二種”2.瀏覽器session超時,TGT不超時“,這時SSO服務器的TGT票據,以及瀏覽器客戶端的TGC(cookie中的TGT)仍然有效。瀏覽器客戶端再次SSO訪問時就可以攜帶TGC(與服務器的TGT對應),向SSO服務器重新發送取得票據ST請求,取得票據ST後,攜帶着有效ST票據可以正常訪問應用系統了。這個過程是瀏覽器客戶端與SSO服務器的一個通訊交互,用戶可能感覺不到,不會出現中斷,好像能連續訪問,這是爲了給用戶一個友好的訪問體驗。明白這個機制,就知道實際上是SSO機制在後臺起作用了。
2.jsessionid問題
jsessionid是java客戶端與應用服務器維持session的一個標識,其他語言客戶端(如php)有其他標識關鍵字,具體是什麼還不太瞭解。jsessionid一般存在於瀏覽器cookie中的(這個一般java客戶端連接到應用服務器會自動執行的),一般情況下不會出現在url中,服務器會從客戶端的cookie中取出來,但是如果瀏覽器禁用了cookie的話,就要重寫url了,顯式的將jsessionid重寫到Url中,方便服務器來通過這個找到session的id。CAS開源單點登錄SSO組件就提供了這個機制。我研究了CAS源碼,基本明白了jsessionid的處理機制。大致原理如下:用戶訪問業務系統,SSO客戶端攔截,重定向到SSO服務器認證時,就將請求路徑uri中寫入";jsessionid=具體的session值",SSO服務器可以分辨出這個標識值與其他客戶端請求不同,進行認證處理,返回的響應給客戶端cookie同時也設置了jsessionid的值,之所以在uri和cookie中都設置了jsessionid,是爲了雙重保障能設置jsessionid值。最後單點登錄成功後,返回業務系統訪問地址也帶有jsessionid參數,這個在uri地址中看起來很彆扭。
提供2種解決方法,如下:
1) 可以在登錄頁面地址的請求地址參數中加入參數”&method=POST“(記住這裏要求POST大寫),這樣就可以在最後返回的訪問uri中不顯示jsessionid。
2)修改代碼如下:
類org.jasig.cas.util.UrlUtils中增加方法cleanupUrl
public static final String cleanupUrl(final String url) { if (url == null) { return null; } final int jsessionPosition = url.indexOf(";jsession"); if (jsessionPosition == -1) { return url; } final int questionMarkPosition = url.indexOf("?"); if (questionMarkPosition < jsessionPosition) { return url.substring(0, url.indexOf(";jsession")); } return url.substring(0, jsessionPosition) + url.substring(questionMarkPosition); } |
類org.jasig.cas.web.flow.DynamicRedirectViewSelector的makeEntrySelection方法中修改如下行
default: // return new ExternalRedirect(serviceResponse.getUrl());//註釋源碼 return new ExternalRedirect(UrlUtils.cleanupUrl(serviceResponse.getUrl()));//清除url中jsessionid |
這樣運行後,url路徑中的jsessionid就不存在了。
3.單點退出時有時子系統未能正常退出
我們知道正常情況下,以用戶A單點登錄系統,正常訪問各子系統,然後執行單點退出時,退出成功後一般跳轉回到登錄頁面要求重新登錄,這時各已登錄的子系統session被銷燬,再次以另一個用戶B登錄進入後,各子系統顯示的應當是用戶B的數據信息。可是有時卻發現有些子系統仍然顯示的是用戶A的數據信息,這屬於偶發現象。現在分析一下產生這種情況可能的原因,找出解決辦法。
若想了解單點退出機制原理,我們可以先看看CAS源碼的SSO單點退出實現機制。參見我的博客中 http://blog.csdn.net/yan_dk/article/details/7095091單點登錄實現機制的【單點退出】部分。
我們看一下源碼,看到退出時調用類AbstractWebApplicationService的方法logOutOfService(),代碼如下
public synchronized boolean logOutOfService(final String sessionIdentifier) { final String logoutRequest = "<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"" HttpURLConnection connection = null; try { connection = (HttpURLConnection) logoutUrl.openConnection(); final BufferedReader in = new BufferedReader(new InputStreamReader(connection while (in.readLine() != null) { return true; |
我們看到紅字部分,在調用發生異常時,代碼是直接返回false的,也就是說單點退出發生錯誤時,SSO服務器並沒有做異常處理,直接返回,這樣就有可能在出現異常時(比如網絡瞬時閃斷),雖然系統界面退出後返回登錄頁面,但是SSO服務器並沒有退出處理,沒有銷燬登錄會話,所以就可能出現沒有真正退出,仍然顯示前一用戶的會話信息。這個應該是CAS源碼的一個bug,解決方法是在此處積累錯誤日誌,並拋出異常處理。這樣應該能解決此問題。修改代碼如下:
。。。 } catch (final Exception e) { 。。。 |
說明:
4.有些請求路徑不需要單點登錄過濾器攔截
業務系統web應用在使用單點登錄組件時,有些請求路徑不需要單點登錄過濾器攔截,比如公共開放的路徑,不需要認證都可以自由訪問的路徑,單點登錄過濾器配置的映射路徑一般以通配符匹配路徑,但要把這些路徑單獨提取出來,讓過濾器不攔截做單點登錄處理,就需要對原有過濾器進行擴展改造,才能實現這個功能。
擴展實現代碼如下:
public class CASFilter implements Filter { public static enum ResponseType { ... public void doFilter( 。。。 CASReceipt receipt = (CASReceipt) session.getAttribute(CAS_FILTER_RECEIPT); if (receipt != null && isReceiptAcceptable(receipt)) { } //過濾器的前置處理 public ResponseType beforeDoSSOFilter(ServletRequest request, } } |
注:主要看原CASFilter 類紅字部分擴展代碼。
擴展實現類BMCASFilter
package com.sitechasia.sso.bmext; public class BMCASFilter extends CASFilter {
|
web.xml文件中配置修改如下:
<filter> ... <init-param> <filter-mapping> 。。。 |
注:紅字部分爲相應配置內容擴展部分
經過上述這樣擴展,配置的排除路徑作爲請求時,單點登錄過濾攔截就會忽略處理,實現了目標功能要求。
5.不同應用服務實現要求SSO客戶端做適應性改造
不同應用服務,對請求的處理方式不同,SSO客戶端常規方法不能處理的需要進行適應性改造。我們先看一下SSO客戶端常規方法處理請求,web應用中定義一個過濾器casfilter,並配置對安全資源攔截,首次登錄系統、或者訪問超時時,安全資源的請求被過濾器casfilter攔截,將安全資源路徑包裝爲service參數,並重定向(http狀態爲302)到SSO服務器地址進行認證,輸入憑證信息(用戶名、密碼等),SSO服務器經過認證、驗證票據機制處理,認證成功後,登錄通過後繼續訪問安全資源,再次訪問安全資源時,過濾器casfilter攔截,根據用戶上下文判斷用戶已經登錄,就不再攔截安全資源,直接正常訪問安全資源。而有些應用中使用其他方式處理請求,按上述常規方法不好處理,下面舉實例來看看解決方案。
5.1.Ajax客戶端
公司有一個應用系統使用Ajax客戶端來處理請求並取得響應,在訪問超時時,Ajax客戶端請求由於使用異步處理技術(只局部刷新頁面),不能將請求繼續302重定向到SSO服務器重新認證處理,不能繼續訪問安全資源,界面響應處於一直延遲狀態,很不友好。我們針對此應用進行適應性改造。改造要點如下:
- 我們約定應用系統的一個Ajax請求的標記(如ajaxRequestFlag=ajaxRequest),在安全資源ajax調用請求時攜帶此請求參數,還約定一個超時請求的響應狀態值(如555),SSO客戶端的過濾器casfilter的相應類程序對此約定進行了相應的改造。
- 應用系統的安全資源頁面中引入文件(HttpStatusSSO.js)SSO對Axax請求的http狀態的處理,其中有超時時的狀態處理、判斷是否登錄、驗證票據等的處理,都是通過ajax請求方式來處理的,其中依賴一個驗證票據的資源ticketValidate.jsp。
經過上述改造後,應用系統的安全資源發送攜帶ajax請求標記的請求,在超時請求時,SSO客戶端返回約定響應碼(555),HttpStatusSSO.js文件判斷處理後轉發給相應的登錄頁面重新認證。單點登錄的訪問也能夠正常的實現功能。這樣只擴展了SSO客戶端組件的實現,向下版本兼容,保證了SSO組件的統一完整性。
上述改造方式是通過實踐開發出來的,可能還有更好的方法來改造擴展,今後持續改進吧。
後記:現在官方的CAS源碼3.4版本已經支持Ajax請求的客戶端,它的實現機制有待於進一步的研究,大家可以參考。
5.2.多域認證
有時我們遇到,不同子系統的認證實現的數據源可能來自一個用戶數據庫。我公司就是這種情況,採用Saas模式運營的軟件,認證的用戶來源於不同的租戶或者域,基於這樣的情況,我們對SSO進行了擴展,主要提供了認證接口,認證實現上,可以在認證時動態取得租戶對應的數據源,對用戶進行認證處理。舉例如下:
5.3.SSO集中認證登錄頁面需要在業務子系統中定製
SSO集中認證的登錄頁面默認是放在SSO服務器端的,樣式也很不好看,用戶需要把登錄頁面放在業務子系統中自行定製,我們對SSO進行了擴展,思路也很簡單,主要是將顯示默認登錄頁面的調用,變成了遠程調用業務子系統的頁面地址,這個地址可以作爲配置參數來配置。舉例如下
=========================================================================================================
這幾天爲了測試人員測試,就把一個tomcat應用整個拷貝了一份,改了下端口一個8080,一個8081,上下文也一樣,結果出問題了:頁面登陸驗證碼死活驗證不過去,最後跟蹤了下後臺發現,登陸界面請求時生成驗證碼並將驗證碼放入session裏面,這個session的id和驗證時從獲取驗證碼的session的的id不一樣,那驗證的時候由於session不一樣,獲取驗證碼肯定爲空,最終驗證失敗。
根據一般的流程,瀏覽器首次發出請求的時候服務端會在response信息裏面給出:
Set-Cookie: JSESSIONID=0000yLsny8JFy4nLxDelrrq9Lx1:-1; Path=/test (這裏假設服務器生成的sessionid爲0000yLsny8JFy4nLxDelrrq9Lx1,應用上下文爲test)要求瀏覽器設置瀏覽器會話cookie,下次請求的時候在request頭信息裏面附帶:
Cookie: JSESSIONID=0000yLsny8JFy4nLxDelrrq9Lx1:-1 服務器以此來判斷是否統一客戶端發出的請求。
而跟蹤我們的應用發現第一次瀏覽器發出請求後,服務端response信息爲:
Set-CookieJSESSIONID=C7A2EB23B029226E6279448D1CFD6207; Path=/test
第二次發出請求的時候整個會話信息爲:
響應頭信息原始頭信息:
Set-Cookie: JSESSIONID=AD54301B809A7D7BBF909F10B4C838AA
; Path=/test
請求頭信息原始頭信息:
Cookie JSESSIONID=4DF629829C6E18953117AB66777ED2CA
瀏覽器雖然第二次請求帶過去的JSESSIONID並不是第一次設置(C7A2EB23B029226E6279448D1CFD6207
),並且服務端又發出了新的Set-Cookie命令和JSESSIONID。
最終纔想起還有另一個tomcat應用的copy修改了本次的cookie session 裏面的JSESSIONID值,
正好跟蹤到信息爲:
Set-Cookie JSESSIONID=4DF629829C6E18953117AB66777ED2CA; Path=/test (這一次請求影響)
Cookie JSESSIONID=C7A2EB23B029226E6279448D1CFD6207(原應用的JSESSIONID)
通過查看火狐瀏覽器裏面兩個應用在同一個主機(localhost)下面只存了一份名爲JSESSIONID的cookie值,結果導致互相影響。
現在tomcat下的一個辦法就是兩個應用其中一個修改下上下文,例如一個是test,另一個是test1 ,這樣由於cookie的路徑(path)在同一個級別下名稱不一樣將不會互相影響。
瀏覽器判斷cookie的應該根據主機,路徑,名稱來判斷,通常根據目錄層級不同作用域也不同,例如:
比如瀏覽器現有三個cookie, path分別爲 "/", "/test", "/test/mgr"
則請求 "/test/other/action.do"時,第1個cookie和第2個cookie會被髮給服務端,第三個不會。
此時瀏覽器請求頭信息裏面的session有可能爲多個,但名稱都一樣如:
Cookie: JSESSIONID=8810E51861891187708C53A1805951A1;JSESSIONID=0000yLsny8JFy4nLxDelrrq9Lx1:-1
這個時候也是出現問題的時候,服務器有可能出現jsessionid不一致的情況,
一個明顯的問題在websphere下由於默認的cookie session的path爲/ 如圖:
這個時候如果同一臺主機或者ip下部署其他應用,如果都是用JSESSIONID作爲key的話,由於/的作用域大,其他的應用將受到影響,首先websphere自己會受到影響,經過測試websphere的每一次請求如下:
Set-Cookie: JSESSIONID=0000yLsny8JFy4nLxDelrrq9Lx1:-1; Path=/
然後後面多次出現信息如下:
相應
Set-Cookie: JSESSIONID= 0000IsxSqg75ELW4C0Y3YCaRgab
:-1; Path=/
請求
Cookie:
JSESSIONID=8810E51861891187708C53A1805951A1(這個爲tomcat的);JSESSIONID=0000yLsny8JFy4nLxDelrrq9Lx1:-1
遇到這種異常情況,請大家從cookie的domain,path,作用域的知識分析下,然後就應該能解決這些問題了,適當的修改下cookie的key名稱,和path。
這裏給看下百度的明顯和其他的不一樣用的是(baiduid作爲key):
~完~