定制的Spring Security(Acegi)的并發(fā)會話過濾器(ConcurrentSessionFilter)的編碼過程(轉(zhuǎn))
定制的Spring Security(Acegi)的并發(fā)會話過濾器(ConcurrentSessionFilter)的編碼過程
上一帖述及使用ConcurrentSessionFilter限制同帳號登錄多次的方法,同帳號多次登錄限制是運(yùn)行系統(tǒng)必需的功能,所以作者對其深入測試,在上一帖中也列舉了Spring Security的ConcurrentSessionFilter和ConcurrentSessionControllerImpl類的幾個(gè)限制。做一下簡單的總結(jié),下面假設(shè)同時(shí)使用DigestProcessingFilter和輔助類:
- 如果exceptionIfMaximumExceeded = true,即第二個(gè)發(fā)起的會話被禁止,如果一個(gè)用戶重新啟動(dòng)瀏覽器,再次登錄失敗,因?yàn)榍耙粋€(gè)會話沒有超時(shí),被當(dāng)成了多次登錄。
- 如果exceptionIfMaximumExceeded = false,如果兩個(gè)人使用同一個(gè)帳號登錄,將出現(xiàn)交互將對方踢出去的現(xiàn)象,實(shí)際上并沒有禁止任何人登錄,只是每次要先將另一個(gè)人踢下去。
需求
我想使用exceptionIfMaximumExceeded = true,同時(shí)允許同一個(gè)用戶在同一臺機(jī)器上連續(xù)登錄多次,我采取了編寫定制的ConcurrentSessionController實(shí)現(xiàn)類的方法。
原理
ConcurrentSessionController是一個(gè)接口,有兩個(gè)需要實(shí)現(xiàn)的方法:checkAuthenticationAllowed()和registerSuccessfulAuthentication(),Spring Security提供了一個(gè)實(shí)現(xiàn)類ConcurrentSessionControllerImpl,經(jīng)過分析缺省的實(shí)現(xiàn)類,發(fā)現(xiàn)方法allowableSessionsExceeded()處理多次并發(fā)會話,在SecurityRegistry中保存每個(gè)會話的信息,主要是用戶帳號對應(yīng)的會話ID(sessionId)和最后發(fā)起時(shí)間,在并發(fā)發(fā)生時(shí),從SecurityRegistry中取出關(guān)于某個(gè)用戶帳號的所有會話,如果exceptionIfMaximumExceeded = false,找到最早一個(gè)會話,將其釋放掉,騰出空間給新會話,如果exceptionIfMaximumExceeded = true,將發(fā)出一個(gè)異常。
所以,需要改進(jìn)allowableSessionsExceeded(),如果exceptionIfMaximumExceeded = true讓程序判斷客戶地址,如果同一個(gè)IP,則允許登錄,將最早的會話釋放掉,如果不是同一個(gè)IP在發(fā)出異常。
在SecurityRegistry中,用戶帳號信息存在一個(gè)對象中,名字是principal,當(dāng)前是一個(gè)Object對象,實(shí)際上只是存了一個(gè)字符串,所以需要擴(kuò)展principal,寫一個(gè)定制的類(我的類含有兩個(gè)屬性:username和userip),里面保存客戶IP信息。allowableSessionsExceeded()只是使用SecurityRegistry,SecurityRegistry中的內(nèi)容是由registerSuccessfulAuthentication()方法寫入的,所以,在該方法中需要將原來的pricipal對象替換成定制的Principal類的對象。同時(shí)checkAuthenticationAllowed()方法也要修改,因?yàn)檫@個(gè)方法要查詢SecurityRegistry,查詢條件替換成定制的Principal類的對象。
注意事項(xiàng)
定制的Principal類要實(shí)現(xiàn)equals()和hashCode()和toString()三個(gè)方法,在equals()方法中只要username相同就表示兩個(gè)對象相同,而在hashCode()中只需要將username的hashcode計(jì)算在內(nèi),因?yàn)镾ecurityRegistry是以principal為關(guān)鍵字的Map容器,這兩個(gè)方法決定了對Map的查詢。toString()方法可以根據(jù)自己的需要寫,我只是將username輸出。
測試
將定制的ConcurrentSessionController對象編制(wire)到應(yīng)用系統(tǒng)中,經(jīng)過測試,能夠達(dá)到預(yù)想目的。
存在的問題
原來想省點(diǎn)勁,只要繼承ConcurrentSessionControllerImpl并重載上述三個(gè)方法就行了,但是不知道為什么securityRegistery屬性一直注入不了,一氣之下,寫了一個(gè)直接實(shí)現(xiàn)ConcurrentSessionController接口的新類。實(shí)際上也不是從頭寫,將ConcurrentSessionControllerImpl代碼改吧該吧即可,用不了幾分鐘,這就是開源的好處。
轉(zhuǎn)自 http://www.gooseeker.com/cn/node/517