2013-01-14 35 views
0

我有一個應用程序與彈簧安全3.1和Ldap集成。以下是在需求的重點和實施至今:彈簧安全3.1破壞現有會話與活躍用戶登錄

  1. 應用程序會爲單個用戶的多個角色,但這些 角色並不在LDAP中存在,所以應用程序驗證只 的用戶名(或用戶ID)來自ldap。

    1. 的作用被分別存儲在數據庫
    2. 當從LDAP成功認證之後,的UserDetails和角色由執行的UserDetailsS​​ervice

    問題集成主要目的自定義UserDetails對象用戶A登錄應用程序

  2. 用戶B登錄應用程序,用戶A會話正在被破壞(這不應該發生,因爲用戶A還沒有註銷! )
  3. 用戶B註銷,用戶A獲取頁面沒有找到,因爲當用戶B在

記錄其會話已毀ApplicationContext的-security.xml文件看起來是這樣的:


<beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> 
    <beans:property name="loginFormUrl" value="/login.jsp" /> 
    <beans:property name="forceHttps" value="true" /> 
</beans:bean> 

<beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter"> 
    <beans:property name="sessionRegistry" ref="sessionRegistry" /> 
    <beans:property name="expiredUrl" value="/login.jsp?login_error=2" /> 
</beans:bean> 

<beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> 
    <beans:constructor-arg value="/login.jsp" /> 
    <beans:constructor-arg> 
     <beans:list> 
      <beans:ref bean="logoutEventBroadcaster" /> 
      <beans:bean id="securityContextLogoutHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" /> 
     </beans:list> 
    </beans:constructor-arg> 
    <beans:property name="filterProcessesUrl" value="/j_spring_security_logout" /> 
</beans:bean> 

<beans:bean id="myAuthFilter" class="com.*.security.CustomAuthenticationProcessingFilter"> 
    <beans:property name="sessionAuthenticationStrategy" ref="sas" /> 
    <beans:property name="authenticationManager" ref="authenticationManager" /> 
    <beans:property name="authenticationFailureHandler" ref="failureHandler" /> 
    <beans:property name="authenticationSuccessHandler" ref="successHandler" />  
</beans:bean> 

<authentication-manager alias="authenticationManager"> 
    <authentication-provider ref="adAuthenticationProvider" /> 
</authentication-manager> 

<beans:bean id="adAuthenticationProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider"> 
    <beans:constructor-arg value="*.*.net" /> 
    <beans:constructor-arg value="ldap://*.*.net:389/" /> 
    <beans:property name="userDetailsContextMapper"> 
     <beans:bean class="com.ezadvice.service.CustomUserDetailsContextMapper" /> 
    </beans:property>  
    <beans:property name="useAuthenticationRequestCredentials" value="true" /> 
</beans:bean> 

<beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> 
    <beans:property name="defaultFailureUrl" value="/login.jsp?login_error=1" /> 
</beans:bean> 

<beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> 
    <beans:property name="defaultTargetUrl" value="/home.do" /> 
    <beans:property name="alwaysUseDefaultTargetUrl" value="true"/> 
</beans:bean> 

<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy"> 
    <beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" /> 
    <beans:property name="maximumSessions" value="1" /> 
    <beans:property name="exceptionIfMaximumExceeded" value="true" /> 
    <beans:property name="migrateSessionAttributes" value="false" /> 
</beans:bean> 

<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" /> 

的CustomAuthenticationProcessingFilter類看起來是這樣的:

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 
     throws AuthenticationException { 

    String roleId = request.getParameter("roleId"); 
    String username = request.getParameter("j_username"); 
    TbEzaLoginHistory tbEzaLoginHistory = null; 

    // check if the user has authority for the role 
    TbEzaUser tbEzaUser = userManagementService.checkUserAndRole(roleId, username); 
    if (null != tbEzaUser) { 
     tbEzaLoginHistory = userManagementService.saveLoginHistory(tbEzaUser, roleId); 
     request.setAttribute("loginHistoryId", tbEzaLoginHistory.getLoginKey()); 
     request.setAttribute("roleId", roleId); 
     request.setAttribute("j_username", username); 
     if (UserTracker.increment(username, roleId)) { 
      try{ 
      Authentication attemptAuthentication = super.attemptAuthentication(request, response); 
      if (null != attemptAuthentication) { 
       CustomUser principal = (CustomUser) attemptAuthentication.getPrincipal(); 
       if (null == principal && null != tbEzaLoginHistory) 
         userManagementService.deleteFromLoginHistory(tbEzaLoginHistory.getLoginKey()); 
       return attemptAuthentication; 
      } 
      } 
      catch (CommunicationException e) { 
       userManagementService.deleteFromLoginHistory(tbEzaLoginHistory.getLoginKey()); 
       UserTracker.decrement(username, roleId);   
       RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp?login_error=5");      
       try { 
        dispatcher.forward(request, response); 
       } catch (ServletException se) { 
        se.printStackTrace(); 
       } catch (IOException ioe) { 
        ioe.printStackTrace(); 
       } 
       LOGGER.debug("Connection Timeout error for UserName:"+username +"\n" + e); 
       e.printStackTrace();      
      } 

     }else { 
      if (null != tbEzaLoginHistory) 
       userManagementService.deleteFromLoginHistory(tbEzaLoginHistory.getLoginKey()); 
      RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp?login_error=4"); 
      try { 
       dispatcher.forward(request, response); 
      } catch (ServletException e) { 
       e.printStackTrace(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 
    } else { 
     RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp?login_error=3"); 
     try { 
      dispatcher.forward(request, response); 
     } catch (ServletException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    if (LOGGER.isDebugEnabled()) { 
     LOGGER.debug(EXITLOGGER + " attemptAuthentication"); 
    } 

    return null; 

} 

@Override 
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException { 
    super.successfulAuthentication(request, response, authResult); 
    UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authResult; 
    WebAuthenticationDetails details = (WebAuthenticationDetails) token.getDetails(); 
    String address = details.getRemoteAddress(); 
    CustomUser user = (CustomUser) authResult.getPrincipal(); 
    String userName = user.getUsername(); 
    System.out.println("Successful login from remote address: " + address + " by username: "+ userName); 
} 


@Override 
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, 
     AuthenticationException failed) throws IOException, ServletException {  
    if (LOGGER.isDebugEnabled()) { 
     LOGGER.debug(ENTRYLOGGER + " unsuccessfulAuthentication"); 
    } 
    try {   
     Long loginHistoryId = (Long) request.getAttribute("loginHistoryId"); 
     String username = (String) request.getAttribute("j_username"); 
     String roleId = (String) request.getAttribute("roleId"); 
     userManagementService.deleteFromLoginHistory(loginHistoryId); 
     super.unsuccessfulAuthentication(request, response, failed); 
     UserTracker.decrement(username, roleId);    
    } catch (Exception e) { 
     e.printStackTrace(); 
    }  

    if (LOGGER.isDebugEnabled()) { 
     LOGGER.debug(EXITLOGGER + " unsuccessfulAuthentication"); 
    } 
} 

的UserTracker類看起來是這樣的:

public class UserTracker { 
private static Set<String> loggedInUsersDetails = new HashSet<String>(); 

@SuppressWarnings("unchecked") 
synchronized public static boolean increment(String userName, String roleId) { 
    if(loggedInUsersDetails.add(userName.toLowerCase()+'~'+roleId)){ 
     return true; 
    }else 
     return false; 

    }  

synchronized public static void decrement(String userName, String roleId) {  
    loggedInUsersDetails.remove(userName.toLowerCase()+'~'+roleId); 
    } 

誰能幫我找出來,爲什麼用戶A的會話被破壞掉了?

+0

可能的應用範圍/單豆(非線程安全)豆保持該會話的參考? (引用用戶A會話在用戶B登錄後立即被刪除) – ben75

+0

我假設spring安全性將負責使應用程序線程安全。或者有什麼我需要設置? – pushpa

+0

我認爲Spring代碼是線程安全的(即使我記得像這樣的一個bug是spring MVC ......但它已被修復)。我更多地考慮自己的代碼中的某些東西。 – ben75

回答

1

docs (SavedRequests and the RequestCache Interface)中,他們討論了ExceptionTranslationFilter作業以在調用AuthenticationEntryPoint之前緩存當前請求。這允許請求被恢復 - 通過SavedRequestAwareAuthenticationSuccessHandler(這是默認值)。

但我注意到另一個evel過濾器:RequestCacheAwareFilter。

在重定向到原始請求之後,RequestCacheAwareFilter被鏈調用,並且他調用'getMatchingRequest()',它獲取請求,然後將其從緩存中刪除!那麼當第二次認證成功時(來自第二個用戶),緩存中沒有URL,所以Spring不知道將我重定向到哪裏。所以我認爲這是問題的根源。

我已經發現了這個問題誕生了,由於這個JIRA: SEC-1241: SavedRequest not destroyed after successful authentication

+0

我能跟蹤了類似的事情,但我不知道如何解決這個問題 – pushpa

+0

這是一件值得在春天的內部...不知道有一個變通:-( – OhadR

0

您可以將您的驗證碼爲自定義的AuthenticationManager。 AuthenticationManager將對LdapAuthenticationProviderDaoAuthenticationProvider有兩個依賴關係。在認證過程中處理它負責:

  • 調用LDAP提供
  • 調用DB提供
  • (從DB從LDAP和角色證書)合併兩個驗證對象爲一體。
+0

我做了使用CustomUserDetailsContextMapper類似的代碼類從LDAP數據庫來實現的UserDetailsContextMapper,通過先驗證,然後調用數據庫供應商,但我會給一個嘗試上述解決方案,以及 – pushpa

+0

你是什麼CustomAuthenticationProcessingFilter背後的主要原因是什麼? –

+0

該過濾器背後的原因是首先檢查用戶如果它是一個有效的用戶,並用正確的作用。同時,我們也已經注意到,併發控制不能正常工作,所以我們不得不寫UserTracker類來維持。 – pushpa

0

終於找到了解決上述問題的辦法。有多種原因:

  1. 在測試上述問題,我犯了一個錯誤,認爲我想,當用戶打開一個瀏覽器標籤的應用,實現併發控制。
  2. Spring內部存儲機器的IP地址,以防止多個用戶從同一臺機器登錄。因此必須進行代碼更改,以便用戶具有多個角色不允許從同一臺計算機登錄。
0

刪除

<beans:property name="maximumSessions" value="1" /> 

<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy"> 
    <beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" /> 
    <beans:property name="maximumSessions" value="1" /> 
    <beans:property name="exceptionIfMaximumExceeded" value="true" /> 
    <beans:property name="migrateSessionAttributes" value="false" /> 
</beans:bean>