|
聲明一下,這篇文章不是基于acegi spring security2.0寫的, 我發(fā)現(xiàn)很多文章都是基于老版本寫的, 并不適用最新版。
下面跟大家分享一下在spring security3.0里如何正宗的做法達到控制多個賬號請求的經驗。
步驟1
view plaincopy to clipboardprint?
<http auto-config="true" > !--session-fixation-protection="none" 防止偽造sessionid攻擊. 用戶登錄成功后會銷毀用戶當前的session.創(chuàng)建新的session,并把用戶信息復制到新session中.--> <session-management invalid-session-url="/common/login.jsp?invalid-session=true" > <concurrency-control error-if-maximum-exceeded="true" max-sessions="1" /> </http> <http auto-config="true" > <!--session-fixation-protection="none" 防止偽造sessionid攻擊. 用戶登錄成功后會銷毀用戶當前的session.創(chuàng)建新的session,并把用戶信息復制到新session中.-->
<session-management invalid-session-url="/common/login.jsp?invalid-session=true" > <concurrency-control error-if-maximum-exceeded="true" max-sessions="1" /> </http>
下面只貼出關鍵部分, 為了不影響閱讀。
注意: 不需要配置 SessionRegistry 等bean( 假設你其他地方不用到的話, 如果用到需要在
<concurrency-control session-registry-ref="sessionRegistry" error-if-maximum-exceeded="true" max-sessions="1" />
加上一個屬性
在做某個管理員踢出一個賬號的時候, SessionRegistry 這個bean是需要用到的。 寫法如下:
view plaincopy to clipboardprint?
@RequestMapping(value = "logout.html") public String logout(String sessionId, String sessionRegistryId, String name, HttpServletRequest request, ModelMap model){ List<Object> userList=sessionRegistry.getAllPrincipals(); for(int i=0; i<userList.size(); i++){ User userTemp=(User) userList.get(i); if(userTemp.getName().equals(name)){ List<SessionInformation> sessionInformationList = sessionRegistry.getAllSessions(userTemp, false); if (sessionInformationList!=null) { for (int j=0; j<sessionInformationList.size(); j++) { sessionInformationList.get(j).expireNow(); sessionRegistry.removeSessionInformation(sessionInformationList.get(j).getSessionId()); String remark=userTemp.getName()+"被管理員"+SecurityHolder.getUsername()+"踢出"; loginLogService.logoutLog(userTemp, sessionId, remark); //記錄注銷日志和減少在線用戶1個 logger.info(userTemp.getId()+" "+userTemp.getName()+"用戶會話銷毀," + remark); } } } } return "auth/onlineUser/onlineUserList.html"; } @RequestMapping(value = "logout.html") public String logout(String sessionId, String sessionRegistryId, String name, HttpServletRequest request, ModelMap model){ List<Object> userList=sessionRegistry.getAllPrincipals(); for(int i=0; i<userList.size(); i++){ User userTemp=(User) userList.get(i); if(userTemp.getName().equals(name)){ List<SessionInformation> sessionInformationList = sessionRegistry.getAllSessions(userTemp, false); if (sessionInformationList!=null) { for (int j=0; j<sessionInformationList.size(); j++) { sessionInformationList.get(j).expireNow(); sessionRegistry.removeSessionInformation(sessionInformationList.get(j).getSessionId()); String remark=userTemp.getName()+"被管理員"+SecurityHolder.getUsername()+"踢出"; loginLogService.logoutLog(userTemp, sessionId, remark); //記錄注銷日志和減少在線用戶1個 logger.info(userTemp.getId()+" "+userTemp.getName()+"用戶會話銷毀," + remark); } } } } return "auth/onlineUser/onlineUserList.html"; } 有時候按文檔和網上配置出來是很華麗, 可事實有時候就是沒有如期運行。
我打開火狐 360瀏覽器, 還是等兩個賬號同時登錄。
無奈之下把源碼下下載剖析(常干的事兒, 喜歡搗騰這些東西)
判斷重復的類是ConcurrentSessionControlStrategy.java下的
checkAuthenticationAllowed這個函數的
view plaincopy to clipboardprint?
final List<SessionInformation> sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false); int sessionCount = sessions.size(); final List<SessionInformation> sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false); int sessionCount = sessions.size();
最重要的一句話是:
sessionInformationList.get(j).expireNow();
這句強制T出了用戶, (設置為過期) 如果想徹底刪除, 加上
sessionRegistry.removeSessionInformation(sessionInformationList.get(j).getSessionId());
即可,
這樣使用getAllPrincipals 則獲取不到被T出的用戶了, 其實原理不是直接刪除User對象, 只結束了它的sessionId,
因為這個User可能不止對應著1個sessionId
我發(fā)現(xiàn), 無論我怎么配置, sessionCount老是煩人的 0。 即使我手動配置了ConcurrentSessionControlStrategy這個bean也沒用(默認會自己調的)
無奈中想自己寫一個自定義的計數器控制, 但細想它這東西不至于這個小問題都出這么大的漏洞吧?
現(xiàn)在的問題是:
如何讓 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>>());
現(xiàn)在的問題變?yōu)榱耍?/div>
如何讓兩個principal , 也就是User, 也就是
public UserDetails loadUserByUsername(String username)
兩次登陸的時候返回的是同一個對象。
那么如何做到兩次在不同瀏覽器登陸的時候返回的是同一個User?
答案是java的基礎, equal hashcode方法重寫。
在User對象里添加以下方法:
view plaincopy to clipboardprint?
public boolean equals(Object object) { if (object instanceof BaseObject) { if (this.id.equals(((BaseObject) object).getId())) return true; } return false; } public int hashCode(){ return this.id.hashCode(); } public boolean equals(Object object) { if (object instanceof BaseObject) { if (this.id.equals(((BaseObject) object).getId())) return true; } return false; } public int hashCode(){ return this.id.hashCode(); } 我就不浪費CSDN的硬盤空間了, 不過還是得貼出最后一句話:
HashMap的key判斷key是否相等也是從hashcode和equals是否相等判斷
從上面可以看出, 我們之前登陸的用戶是存在 Map<Object,Set<String>> principals 的。
這個存儲結構是, 一個User 對應多個Set集合的sessionId。
所以要判斷用戶是否已存在登陸的了, 當然要重寫這2個方法。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/sinlff/archive/2010/09/18/5892531.aspx
|
|
|