前年我寫過一篇隨筆抱怨Microsoft在ASP.NET架構中Session_End事件上處理,說來慚愧,其實當年我對ASP.NET運行時的復雜性理解不足。實話說,捕捉通過身份驗證和注銷身份驗證對我來說,意義重大。例如:
在登錄前先檢查是否已經存在SSO提供器;
登錄完成後加載相關的權限,這些加載過程可能與具體應用項目完全無關;
登錄結束後通知SSO提供器清除Cookie內容;
......
目前的ASP.NET提供的解決方案是在Global.cs中加上FormsAuthentication_OnAuthenticated方法來捕捉已通過驗證事件。該方法的缺陷是:
1.只能捕捉Forms身份驗證方式,而不能捕捉Windows和Passport認證方式;
2.只能捕捉已通過身份驗證事件而不能捕捉身份注銷事件;
3.必須修改global.cs文件。
以上任何一個缺陷都是我無法接受的。當時在ASP.NET1.1解決那個問題時用了五六個接口,十多個類,並且有一個輸出類要求應用程序登入和注銷時訪問相應的方法,而不是自由地使用FormsAuthentication的相關方法。現如今該問題總算比較滿意地解決了。思路是這樣的:
在一個HttpModule中建立兩張會話表,一張記錄已通過身份驗證的會話;另一張記錄未通過身份驗證的會話,這樣,在HttpApplication.AcquireRequestState事件中查找每個會話在這兩張表中的狀態:
狀態一 兩張表中都沒有 這是一個新的會話
狀態二 在已通過身份驗證的會話表中 已通過身份驗證
狀態三 在未通過身份驗證的會話表中 未通過身份驗證
如果是狀態一,則立即調用所有SSO提供器的身份查驗方法,只要有任何一個SSO提供器證實已經通過了身份驗證,則立即將狀態調整到狀態二,並通知所有訂閱身份狀態變化的Handler。如果是狀態二或狀態三,則立即與會話的實際身份狀態進行比對。會話實際的身份狀態可以通過查詢HttpContext.User來獲得。如果二者不同,則根據情況調整表中所記錄的狀態,並向訂閱身份變化的Handler發出相應的通知。
有一個問題是:會話列表的查詢頻度非常高,每次Request都不可避免查詢一次。所以這裡對算法的選擇要求較高。我在實際的項目中選擇了字符串數組的BinarySearch算法。這樣每次添加或刪除新的會話時不可避免對字符串在數組中的位置進行調整,以保持排序狀態。當然,在比對過程中也需要根據命中率調整比對順序,例如三種狀態中,顯然狀態二的比例最高(當然數組往往也最龐大),應該優先選擇。
最後的解決方案是:只用了三個接口,一個HttpModule和幾個內部類就實現了,完全不必修改global.cs,且沒有任何輸出類供登錄認證模塊調用,所有的SSO提供器也只需要通過web.config來配置,對業務層是完全透明的。這三個接口是:一個配置參數上下文接口、一個SSO提供器接口(同時兼做捕捉身份狀態變化的Handler接口)、一個Handler接口的工廠接口(以保持Handler接口的構造器自由以及決定是否建立Handler接口的實現類實例)。