項目技術背景:
1:asp.net mvc 3 項目
2:登錄機制是采用membership
3:認證采用Forms驗證
4:EntityFramwork做為數據訪問
問題描述:
當我們的項目部署到租用的美國服務器上后 (據說是一個雲服務,說的通俗點可能就是在虛似機上的一個IIS站點)出現了用戶需要頻繁登錄的現象,具體來講大約在10分鍾之內就會要求用戶重新登錄。
階段一:
問題是客戶發現的,他們在UAT時,提到了用戶需要頻繁登錄的問題。
我的反應:
由於我提供給大家UAT的是一個公共帳號,大家均使用同一帳號登錄,可能會發生不同的人注銷同一帳號的現象,可能存在相互的影響,故建議用戶重新啟用一個全新的帳號。
問題:上面的項目技術背景提到了是采用Forms驗證,而我們是采用默認的Forms驗證機制,即將數據存儲在客戶端,以cookie的形式,故即使多人使用同一帳號,發生不同用戶注銷同一帳戶的現象,也不會影響到其它的用戶的登錄狀態。這也是當時沒太注意的地方,沒找出問題本質的情況下,憑經驗下結論。
階段二:
客戶再次反應此問題,而且他們明確了自己使用的帳戶是私人的。
我的反應:
意識到事情的嚴重性,馬上組織測試人員做了仔細的測試,發現問題確實存在,當時就開始着手解決這個問題,畢竟這屬於一個優先級最高的問題。
經過和同事討論以及網上搜索后得到如下方案:
1:在web.config中設置forms的一個time out
我們原來的配置如下:
2:也許和session的設置有關系
我也忘記當時為什么聯想到session了,也許是我原來的項目都是基於sessio機制的驗證原因吧,但同樣的問題是,session的默認機制也是存儲在進程中,且超時時間為20分鍾,我同樣抱着試試的心情,將這個數值調整到300分鍾,問題依舊。
3:考慮是否和Membersip的設置有關系
因為我們登錄是采用membership,是否是它有相應的設置,於是開始咨詢同事,結果是,我們的項目是第一個基於membership的項目,網上搜索無果。
4:懷疑服務器提供商的產品會定期回收我們站點的應用程序池
這是有依據的,因為我們在自己的本地測試機器上做測試,一點問題都沒有,從不出現頻繁登錄的現象。於時讓相關同事發郵件咨詢服務器提供商,以等待結果。
5:做好服務器提供商無法給出滿意答案的准備。
如果應用程序池會被定期回收,而服務器提供商無法即時解決被回收的問題時,那么我們嘗試將session存入在數據庫中。畢竟我們不能一直等別人的反饋。
問題:這也是沒有搞清楚Forms驗證的機制,錯誤的想法,誤認為和session有關系,好的是,我們當時只有這個想法 ,並沒有着手去做,因為它有如下問題:
1):這是一種逃避問題的方式。
即使存儲在進程中有問題,那么我們需要找出其中的原因,而不能因為它有問題就想其它的方法,沒有治本。
2):session存儲中數據庫中需要部署很多的數據庫腳本。
這樣一來是工作量增大,二來對現在的系統會有一定影響。
3):我們租用的數據庫空間很少,只有幾十M,怕影響業務數據的使用。
由於服務器提供商未在短時間內給出正確答案,我開始從技術手段下手,從根本上排查原因。
此次對Forms驗證做了比較詳細的了解,排除了session和membership的干擾,講的簡單點Forms驗證原理是這樣的:
首先創建一個用戶的票據,經過相應算法的加密,最后將票據保存在cookie中,驗證的過程是首先讀取用戶cookie,如果存在的話,再將票據解密,如果沒有問題即驗證通過。
於時,開始手動編寫登錄邏輯,原來是直接調用授權代碼:
現在開始嘗試以編程方式來授權,即不從配置文件中讀取相關設置,於時有了如下代碼:
true, UserRole); // User datastring encryptedTicket = FormsAuthentication.Encrypt(authTicket); //加密
// 存入Cookie HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
authCookie.Expires = authTicket.Expiration;
Response.Cookies.Add(authCookie);
興奮的部署上去之后,仍然是失望而歸。
再次嘗試對讀取cookie做手動編碼:
// get Cookie.
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if ( null == authCookie)
return;
FormsAuthenticationTicket authTicket = null;
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if ( null == authTicket)
return;
string[] roles = authTicket.UserData.Split( new char[] { ' , ' });
FormsIdentity id = new FormsIdentity(authTicket);
GenericPrincipal principal = new GenericPrincipal(id, roles);
Context.User = principal;
還是失敗則歸。
最后在網上查看到,如果應用程序池被回收,它會重新分配一個machineKey,而這個machineKey會影響到cookie票據,它有兩個重要的屬性:
validationKey,
decryptionKey
它和上面說到的對票據數據的加密,解密,驗證問題有直接關系,這里我就不多講了。如果應用程序池被回收,那么對應的validationKey就會不相同,即與之前的cookie就無法匹配上,這樣就造成了我們需要頻繁登錄。
微軟也非常建議在web項目中配置明確的machineKey,理由如下:
1:可以解決站點重啟,或者是應用程序池回收造成用戶驗證失敗的現象。
2:如果是負載均衡的站點,那么他們之間最好也共用同一明確的machineKey。
問題總算找到了,問題也解決了,現在的問題就是應用程序池為什么會被定期回收,這個問題的解釋就不是我們開發組能回答的問題了,我們完成解決了對他們的依賴,即使他們不做解釋不做處理,我們的問題同樣也得到了解決,從而不會影響到用戶體驗以及最終項目上線。
總結:
之所以這樣一個問題困擾我們這么些天(大約一周時間),首要原因是沒有詳細了解Forms驗證的機制,這是我做的第一個Forms驗證項目,所以走了些彎路,容易憑經驗被一些其它信息所干擾。其次是租用服務器問題,我們對於他的定期回收原因,還在排查中,如果不定期回收,此問題也不會出現。所以當出現修改默認值不起作用時,請轉換你的思維方式去嘗試從不同角度分析問題。
參考資料:http://msdn.microsoft.com/en-us/library/ff649308.aspx