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進行了擴展,思路也很簡單,主要是將顯示默認登錄頁面的調用,變成了遠程調用業務子系統的頁面地址,這個地址可以作爲配置參數來配置。舉例如下: