1. 程式人生 > >spring Security 重複登入配置無效的問題

spring Security 重複登入配置無效的問題

關於spring Security重複登入的配置,百度一大堆,我這裡就不囉嗦了。


今天碰到 按照網上的配置,但是 感覺配置無效,同一使用者還是可以登入,不知道為什麼,開始以為是自己配置的又問題。再三確認感覺自己的配置沒有一點問題,


所有查詢原因:檢視原始碼 發現 org.springframework.security.core.session.SessionRegistryImpl 類中的 


 public List<SessionInformation> getAllSessions(Object principal, boolean includeExpiredSessions) {
        final Set<String> sessionsUsedByPrincipal

= principals.get(principal);


        if (sessionsUsedByPrincipal == null) {
            return Collections.emptyList();
        }


的 sessionsUsedByPrincipal  一直為null。


直接把final Set<String> sessionsUsedByPrincipal 

= principals.get(principal); 放入百度中搜索,果然找到了自己想要的文章:

連線為:http://sb33060418.iteye.com/blog/1953515


當然要主要修改自己的 public class XaUserDetails implements UserDetails

@Override  
public boolean equals(Object rhs) {  
   if (rhs instanceof XaUserDetails) {  
       return username.equals(((XaUserDetails)

rhs).getUsername());  
   }  
   return false;  
}
 
/** 
* Returns the hashcode of the {@code username}. 
*/  
@Override  
public int hashCode() {  
   return username.hashCode();  


這裡記的要改成 自己的類哦。


害怕大神把他的文章刪掉了,我自己複製下來,為自己以後檢視方便,再次感謝大神。


在開發系統認證授權時,經常會碰到需要控制單個使用者重複登入次數或者手動踢掉登入使用者的需求。如果使用Spring Security 3.1.x該如何實現呢? 

Spring Security中可以使用session management進行會話管理,設定concurrency control控制單個使用者並行會話數量,並且可以通過程式碼將使用者的某個會話置為失效狀態以達到踢使用者下線的效果。 

本次實踐的前提是已使用spring3+Spring Security 3.1.x實現基礎認證授權。 

1.簡單實現 

要實現會話管理,必須先啟用HttpSessionEventPublisher監聽器。 
修改web.xml加入以下配置 

Java程式碼  收藏程式碼
  1. <listener>  
  2.     <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>  
  3. </listener>  

如果spring security是簡單的配置,如 
Java程式碼  收藏程式碼
  1. <http use-expressions="true" access-denied-page="/login/noRight.jsp"   
  2.         auto-config="true">  
  3.     <form-login login-page="/login/login.jsp" default-target-url="/inde.jsp"   
  4.         authentication-failure-url="/login/login.jsp" always-use-default-target="true"/>  
  5. ...  
  6. </http>  

且沒有使用自定義的entry-point和custom-filter,只要在<http></http>標籤中新增<session-management>就可以是實現會話管理和並行控制功能,配置如下 
Java程式碼  收藏程式碼
  1. <!-- 會話管理 -->  
  2. <session-management invalid-session-url="/login/logoff.jsp">  
  3.     <!-- 並行控制 -->  
  4.     <concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>  
  5. </session-management>  

其中invalid-session-url是配置會話失效轉向地址;max-sessions是設定單個使用者最大並行會話數;error-if-maximum-exceeded是配置當用戶登入數達到最大時是否報錯,設定為true時會報錯且後登入的會話不能登入,預設為false不報錯且將前一會話置為失效。 
配置完後使用不同瀏覽器登入系統,就可以看到同一使用者後來的會話不能登入或將已登入會話踢掉。 

2.自定義配置 

如果spring security的一段<http/>中使用了自定義過濾器<custom-filter/>(特別是FORM_LOGIN_FILTER),或者配置了AuthenticationEntryPoint,或者使用了自定義的UserDetails、AccessDecisionManager、AbstractSecurityInterceptor、FilterInvocationSecurityMetadataSource、UsernamePasswordAuthenticationFilter等,上面的簡單配置可能就不會生效了,Spring Security Reference Documentation裡面3.3.3 Session Management是這樣說的: 
Java程式碼  收藏程式碼
  1. If you are using a customized authentication filter for form-based login, then you have to configure concurrent session control support explicitly. More details can be found in the Session Management chapter.  

按照文章第12.3章中說明,auto-config已經失效,就需要自行配置ConcurrentSessionFilter、ConcurrentSessionControlStrategy和SessionRegistry,雖然配置內容和預設一致。配置如下: 
Java程式碼  收藏程式碼
  1. <http use-expressions="true" access-denied-page="/login/noRight.jsp" ...   
  2.     auto-config="false">  
  3.     <!-- 登入fliter配置 -->  
  4.     <custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />  
  5.     <custom-filter position="FORM_LOGIN_FILTER"   
  6.         ref="myUsernamePasswordAuthenticationFilter" />  
  7.     <session-management   
  8.         session-authentication-strategy-ref="sessionAuthenticationStrategy"   
  9.         invalid-session-url="/login/logoff.jsp"/>  
  10. ...  
  11. </http>  
  12. ...  
  13. <beans:bean id="myUsernamePasswordAuthenticationFilter"   
  14.     class="com.sunbin.login.security.MyUsernamePasswordAuthenticationFilter">  
  15.     <beans:property name="sessionAuthenticationStrategy"   
  16.     ref="sessionAuthenticationStrategy" />  
  17.     <beans:property name="authenticationManager" ref="authenticationManager" />  
  18. </beans:bean>  
  19. <!-- sessionManagementFilter -->  
  20. <beans:bean id="concurrencyFilter"  
  21.     class="org.springframework.security.web.session.ConcurrentSessionFilter">  
  22.     <beans:property name="sessionRegistry" ref="sessionRegistry" />  
  23.     <beans:property name="expiredUrl" value="/login/logoff.jsp" />  
  24. </beans:bean>  
  25. <beans:bean id="sessionAuthenticationStrategy"  
  26.     class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">  
  27.     <beans:constructor-arg name="sessionRegistry"  
  28.         ref="sessionRegistry" />  
  29.     <beans:property name="maximumSessions" value="1" />  
  30. </beans:bean>  
  31. <beans:bean id="sessionRegistry"  
  32.     class="org.springframework.security.core.session.SessionRegistryImpl" />  

如果沒有什麼問題,配置完成後就可以看到會話管理的效果了。 
需要和簡單配置一樣啟用HttpSessionEventPublisher監聽器。 

3.會話管理 

很多人做完第二步以後可能會發現,使用不同瀏覽器先後登入會話還是不受影響,這是怎麼回事呢?是配置的問題還是被我忽悠了?我配置的時候也出現過這個問題,除錯時看到確實走到了配置的sessionRegistry裡卻沒有效果,在網上找了很久也沒有找到答案,最後還是隻能出動老辦法:檢視原始碼。 

ConcurrentSessionControlStrategy原始碼部分如下: 
Java程式碼  收藏程式碼
  1. public void onAuthentication(Authentication authentication, HttpServletRequest request,  
  2.         HttpServletResponse response) {  
  3.     checkAuthenticationAllowed(authentication, request);  
  4.   
  5.     // Allow the parent to create a new session if necessary  
  6.     super.onAuthentication(authentication, request, response);  
  7.     sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());  
  8. }  
  9.   
  10. private void checkAuthenticationAllowed(Authentication authentication, HttpServletRequest request)  
  11.         throws AuthenticationException {  
  12.   
  13.     final List<SessionInformation> sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);  
  14.   
  15.     int sessionCount = sessions.size();  
  16.     int allowedSessions = getMaximumSessionsForThisUser(authentication);  
  17.   
  18.     if (sessionCount < allowedSessions) {  
  19.         // They haven't got too many login sessions running at present  
  20.         return;  
  21.     }  
  22.   
  23.     if (allowedSessions == -1) {  
  24.         // We permit unlimited logins  
  25.         return;  
  26.     }  
  27.   
  28.     if (sessionCount == allowedSessions) {  
  29.         HttpSession session = request.getSession(false);  
  30.   
  31.         if (session != null) {  
  32.             // Only permit it though if this request is associated with one of the already registered sessions  
  33.             for (SessionInformation si : sessions) {  
  34.                 if (si.getSessionId().equals(session.getId())) {  
  35.                     return;  
  36.                 }  
  37.             }  
  38.         }  
  39.         // If the session is null, a new one will be created by the parent class, exceeding the allowed number  
  40.     }  
  41.   
  42.     allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);  
  43. }  
  44.   
  45. ...  
  46.   
  47. protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions,  
  48.         SessionRegistry registry) throws SessionAuthenticationException {  
  49.     if (exceptionIfMaximumExceeded || (sessions == null)) {  
  50.         throw new SessionAuthenticationException(messages.getMessage("ConcurrentSessionControlStrategy.exceededAllowed",  
  51.                 new Object[] {Integer.valueOf(allowableSessions)},  
  52.                 "Maximum sessions of {0} for this principal exceeded"));  
  53.     }  
  54.   
  55.     // Determine least recently used session, and mark it for invalidation  
  56.     SessionInformation leastRecentlyUsed = null;  
  57.   
  58.     for (SessionInformation session : sessions) {  
  59.         if ((leastRecentlyUsed == null)  
  60.                 || session.getLastRequest().before(leastRecentlyUsed.getLastRequest())) {  
  61.             leastRecentlyUsed = session;  
  62.         }  
  63.     }  
  64.   
  65.     leastRecentlyUsed.expireNow();  
  66. }  

checkAuthenticationAllowed是在使用者認證的時候被onAuthentication呼叫,該方法首先呼叫SessionRegistryImpl.getAllSessions(authentication.getPrincipal(), false)獲得使用者已登入會話。如果已登入會話數小於最大允許會話數,或最大允許會話數為-1(不限制),或相同使用者在已登入會話中重新登入(有點繞口,但有時候會有這種使用者自己在同一會話中重複登入的情況,不注意就會重複計數),就呼叫SessionRegistry.registerNewSession註冊新會話資訊,允許本次會話登入;否則呼叫 
allowableSessionsExceeded方法丟擲異常或最老的會話置為失效。 

接下來看SessionRegistryImpl類的原始碼,關鍵就是getAllSessions方法: 
Java程式碼  收藏程式碼
  1. public List<SessionInformation> getAllSessions(Object principal, boolean includeExpiredSessions) {  
  2.     final Set<String> sessionsUsedByPrincipal = principals.get(principal);  
  3.   
  4.     if (sessionsUsedByPrincipal == null) {  
  5.         return Collections.emptyList();  
  6.     }  
  7.   
  8.     List<SessionInformation> list = new ArrayList<SessionInformation>(sessionsUsedByPrincipal.size());  
  9.   
  10.     for (String sessionId : sessionsUsedByPrincipal) {  
  11.         SessionInformation sessionInformation = getSessionInformation(sessionId);  
  12.   
  13.         if (sessionInformation == null) {  
  14.             continue;  
  15.         }  
  16.   
  17.         if (includeExpiredSessions || !sessionInformation.isExpired()) {  
  18.             list.add(sessionInformation);  
  19.         }  
  20.     }  
  21.   
  22.     return list;  
  23. }  

SessionRegistryImpl自己維護一個private final ConcurrentMap<Object,Set<String>> principals,並以使用者資訊principal作為key來儲存某一使用者所有已登入會話編號。 

再次除錯程式碼時發現,principals中明明有該使用者principal但principals.get(principal)取到的是null,然後認證成功,又往principals裡面put了一個新的principal物件為key。檢視debug控制檯發現principals中兩次登入的principal內容一致,但卻無法從map中取得,這說明新登入的principal和舊的不相等。 

再檢視ConcurrentHashMap.get(Object key)方法原始碼就能找到問題了。我們知道Map中取值的時候都是要邏輯上相等的,即hash值相等且equals。如果兩次登入的principal邏輯上不相等,自然被認為是兩個使用者,不會受最大會話數限制了。 

這裡會話管理不生效的原因是在自定義的UserDetails。一般配置Spring Security都會自己實現使用者資訊介面 
Java程式碼  收藏程式碼
  1. public class User implements UserDetails, Serializable  

並實現幾個主要方法isAccountNonExpired()、getAuthorities()等,但卻忘記重寫繼承自Object類的equals()和hashCode()方法,導致使用者兩次登入的資訊無法被認為是同一個使用者。 

檢視Spring Security的使用者類org.springframework.security.core.userdetails.User原始碼 
Java程式碼  收藏程式碼
  1. /** 
  2.  * Returns {@code true} if the supplied object is a {@code User} instance with the 
  3.  * same {@code username} value. 
  4.  * <p> 
  5.  * In other words, the objects are equal if they have the same username, representing the 
  6.  * same principal. 
  7.  */  
  8. @Override  
  9. public boolean equals(Object rhs) {  
  10.     if (rhs instanceof User) {  
  11.         return username.equals(((User) rhs).username);  
  12.     }  
  13.     return false;  
  14. }  
  15.   
  16. /** 
  17.  * Returns the hashcode of the {@code username}. 
  18.  */  
  19. @Override  
  20. public int hashCode() {  
  21.     return username.hashCode();  
  22. }  

只要把這兩個方法加到自己實現的UserDetails類裡面去就可以解決問題了。 

4.自己管理會話 

以下部分內容參考wei_ya_wen的http://blog.csdn.net/wei_ya_wen/article/details/8455415這篇文章。 

管理員踢出一個賬號的實現參考如下: 
Java程式碼  收藏程式碼
  1. @RequestMapping(value = "logout.html")   
  2. public String logout(String sessionId, String sessionRegistryId, String name, HttpServletRequest request, ModelMap model){      
  3.     List<Object> userList=sessionRegistry.getAllPrincipals();    
  4.     for(int i=0; i<userList.size(); i++){    
  5.         User userTemp=(User) userList.get(i);        
  6.         if(userTemp.getName().equals(name)){            
  7.             List<SessionInformation> sessionInformationList = sessionRegistry.getAllSessions(userTemp, false);    
  8.             if (sessionInformationList!=null) {     
  9.                 for (int j=0; j<sessionInformationList.size(); j++) {    
  10.                     sessionInformationList.get(j).expireNow();    
  11.                     sessionRegistry.removeSessionInformation(sessionInformationList.get(j).getSessionId());    
  12.                     String remark=userTemp.getName()+"被管理員"+SecurityHolder.getUsername()+"踢出";    
  13.                     loginLogService.logoutLog(userTemp, sessionId, remark);     //記錄登出日誌和減少線上使用者1個    
  14.                     logger.info(userTemp.getId()+"  "+userTemp.getName()+"使用者會話銷燬," + remark);    
  15.                 }    
  16.             }    
  17.         }    
  18.     }    
  19.     return "auth/onlineUser/onlineUserList.html";    
  20. }    

如果想徹底刪除, 需要加上 
Java程式碼  收藏程式碼

    相關推薦

    spring Security 重複登入配置無效的問題

    關於spring Security重複登入的配置,百度一大堆,我這裡就不囉嗦了。 今天碰到 按照網上的配置,但是 感覺配置無效,同一使用者還是可以登入,不知道為什麼,開始以為是自己配置的又問題。再三確認感覺自己的配置沒有一點問題, 所有查詢原因:檢視原始碼 發現 o

    spring security 採用 資料庫配置檢測使用者登入,並跳轉不同頁面

    applicationContext-security.xml <?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schem

    spring security實現登入驗證以及根據使用者身份跳轉不同頁面

    想關依賴,採用session加redis儲存使用者資訊 <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security

    Spring-Security整合CAS之Spring-Security.xml部分配置詳解

    <?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://w

    Spring Security 實現 antMatchers 配置路徑的動態獲取

    1. 為什麼要實現動態的獲取 antMatchers 配置的資料           這兩天由於公司專案的需求,對 spring security 的應用過程中需要實現動態的獲取 antMatch

    利用spring-security實現登入

    首先建立maven工程(war) pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:sc

    Spring+SpringMVC重複載入配置檔案問題

    sping+springmvc的框架中,IOC容器的載入過程 http://my.oschina.net/xianggao/blog/596476 基本上是先載入ContextLoaderListener,然後生成一個ioc容器。 然後再例項化DispatchServlet

    spring security oauth2.0配置詳解

    spring security oauth2.0 實現 目標 現在很多系統都支援第三方賬號密碼等登陸我們自己的系統,例如:我們經常會看到,一些系統使用微信賬號,微博賬號、QQ賬號等登陸自己的系統,我們現在就是要模擬這種登陸的方式,很多大的公司已經實現了這

    SpringMVC+Spring Security實現登入認證的簡單功能

    一、依賴pom.xml 這裡僅僅列出security需要的依賴,其他依賴見前面Spring目錄下文章。 <!-- Spring Security -->      <dependency>          <groupId>org

    Spring-Security完整版配置

    package com.niugang.config; import java.io.IOException; import javax.servlet.ServletException; import org.springframework.beans.factory.

    spring security預設引數配置

    Spring Security 內建屬性引數 Spring Boot 提供的內建配置引數以security為字首,具體屬性如下: # SECURITY (SecurityProperties 類中) security.basic.authorize-mode=role 

    spring security 基礎入門(配置詳解)

    <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterPr

    springboot整合spring-security實現登入控制的過程及其要點

    前言 首先,整合spring-security的目的 1,實現登入控制; 2,防止同一賬號的同時多處登入。 3,實現臺介面的訪問許可權控制。 實現方式不止一種,選擇spring-security是因為它夠簡潔。 實現 闡述兩種實現方式--不用框架、採用spri

    spring boot security 防止使用者重複登入(原創)

    原理:在認證成功通過後,在顯示登入成功頁面之前,也就是在SavedRequestAwareAuthenticationSuccessHandler類中操作。 新增一個集合sessionMap 用於儲存認證成功的會話,鍵名為會話ID, 每次有使用者登入認證通過都要判斷一下是否重複登入

    spring-security 個性化使用者認證流程——自定義登入頁面(可配置

    1.定義自己的登入頁面我們需要根據自己的業務系統構建自己的登入頁面以及登入成功、失敗處理在spring security提供給我的登入頁面中,只有使用者名稱、密碼框,而自帶的登入成功頁面是空白頁面(可以重定向之前請求的路徑中),而登入失敗時也只是提示使用者被鎖定、過期等資訊。 在實際的開發中,則需要更

    spring security起步三:自定義登入配置與form-login屬性詳解

    在上一篇部落格spring security起步二:自定義登入頁中我們實現瞭如何自定義登入頁,但是還存在很多問題: 1.spring security如何對登入請求進行攔截 2.登入成功後如何跳轉 3.登入失敗後如何跳轉 form-login屬性詳解

    (一)如何使用Spring-security來實現登入驗證功能(XML配置方式)?

    先從使用xml的方式來實現使用者的許可權登入 (1)需要在maven工程中加上關於spring-secutity的jar包的依賴 //spring-securityd 有關的依賴 <

    使用spring-security配置資料庫登入&密碼加密

    直接上程式碼security.xml <?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" x

    [Spring Security] 表單登入通過配置自定義登入頁面,以及自定義認證成功/失敗處理機制

    1.目錄結構2.配置自定義登入頁通過配置SecurityProperties,MyProperties,SecurityCoreConfig來讀取resources資料夾下application.properties中相關配置項。SecurityProperties:pack

    spring security 登入、許可權管理配置

    登入流程 1)容器啟動(MySecurityMetadataSource:loadResourceDefine載入系統資源與許可權列表)  2)使用者發出請求  3)過濾器攔截(MySecurityFilter:doFilter)  4)取得請求資源所需許可權(MySe