聲明一下,這篇文章不是基於acegi spring security2.0寫的, 我發現很多文章都是基於老版本寫的, 並不適用最新版。
下面跟大家分享一下在spring security3.0裏如何正宗的做法達到控制多個賬號請求的經驗。
步驟1
下面只貼出關鍵部分, 爲了不影響閱讀。
注意: 不需要配置 SessionRegistry 等bean( 假設你其他地方不用到的話, 如果用到需要在
<concurrency-control session-registry-ref="sessionRegistry" error-if-maximum-exceeded="true" max-sessions="1" />
加上一個屬性
在做某個管理員踢出一個賬號的時候, SessionRegistry 這個bean是需要用到的。 寫法如下:
有時候按文檔和網上配置出來是很華麗, 可事實有時候就是沒有如期運行。
我打開火狐 360瀏覽器, 還是等兩個賬號同時登錄。
無奈之下把源碼下下載剖析(常乾的事兒, 喜歡搗騰這些東西)
判斷重複的類是ConcurrentSessionControlStrategy.java下的
checkAuthenticationAllowed這個函數的
最重要的一句話是:
sessionInformationList.get(j).expireNow();
這句強制T出了用戶, (設置爲過期)
如果想徹底刪除, 加上
sessionRegistry.removeSessionInformation(sessionInformationList.get(j).getSessionId());
即可,
這樣使用getAllPrincipals 則獲取不到被T出的用戶了, 其實原理不是直接刪除User對象, 只結束了它的sessionId,
因爲這個User可能不止對應着1個sessionId
我發現, 無論我怎麼配置, sessionCount老是煩人的 0。 即使我手動配置了ConcurrentSessionControlStrategy這個bean也沒用(默認會自己調的)
無奈中想自己寫一個自定義的計數器控制, 但細想它這東西不至於這個小問題都出這麼大的漏洞吧?
現在的問題是:
如何讓 int sessionCount = sessions.size(); 這句在第二個賬號登陸的時候不爲0。
於是我進入了sessionRegistry.getAllSessions(authentication.getPrincipal(), false); 這個函數。
也就是SessionRegistryImpl.java
public List<SessionInformation> getAllSessions(Object principal, boolean includeExpiredSessions) {
final Set<String> sessionsUsedByPrincipal = principals.get(principal);
這個函數是通過在一個HashMap裏拿到key value的。
而principals的聲明這樣寫。
private final Map<Object,Set<String>> principals = Collections.synchronizedMap(new HashMap<Object,Set<String>>());
現在的問題變爲了:
如何讓兩個principal , 也就是User, 也就是
public UserDetails loadUserByUsername(String username)
兩次登陸的時候返回的是同一個對象。
那麼如何做到兩次在不同瀏覽器登陸的時候返回的是同一個User?
答案是java的基礎, equal hashcode方法重寫。
在User對象裏添加以下方法:
涉及這方面的基礎請參考 http://blog.csdn.net/willielee/archive/2010/08/11/5804463.aspx
我就不浪費CSDN的硬盤空間了, 不過還是得貼出最後一句話:
HashMap的key判斷key是否相等也是從hashcode和equals是否相等判斷
從上面可以看出, 我們之前登陸的用戶是存在 Map<Object,Set<String>> principals 的。
這個存儲結構是, 一個User 對應多個Set集合的sessionId。
所以要判斷用戶是否已存在登陸的了, 當然要重寫這2個方法。