2012-02-09 56 views
3

我在使用Spring Security,我想知道哪些用戶當前在線。我首先嚐試了使用SessionRegistryImpl<session-management session-authentication-strategy-ref="..." ... />的方法,但我猜這個List存儲在內存中,我想避免它(這將是一個巨大的網站,並且很多用戶將同時在線,List可以變得巨大)。如果我錯了,請糾正我。Spring Security的在線用戶

我嘗試的第二種方法是使用監聽器和HttpSessionListener接口和自定義AuthenticationManager並在數據庫中存儲「is online flag」。基本上,我的身份驗證管理器的authenticate(...)方法中的標誌設置爲true,並在我的會話偵聽器的sessionDestroyed(...)方法中設置爲false。

的web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
     version="2.5"> 

    <display-name>Test</display-name> 

    <context-param> 
     <param-name>contextConfigLocation</param-name> 
     <param-value>/WEB-INF/security.xml</param-value> 
    </context-param> 

    <!-- Security --> 
    <filter> 
     <filter-name>springSecurityFilterChain</filter-name> 
     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
    </filter> 

    <filter-mapping> 
     <filter-name>springSecurityFilterChain</filter-name> 
     <url-pattern>/*</url-pattern> 
    </filter-mapping> 

    <listener> 
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
    </listener> 

    <listener> 
     <listener-class>my.package.SessionListener</listener-class> 
    </listener> 

    <servlet> 
     <servlet-name>test</servlet-name> 
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
     <load-on-startup>1</load-on-startup> 
    </servlet> 

    <servlet-mapping> 
     <servlet-name>test</servlet-name> 
     <url-pattern>/</url-pattern> 
    </servlet-mapping> 

    <session-config> 
     <session-timeout>1</session-timeout> 
    </session-config> 
</web-app> 

春季安全配置:

<beans:beans xmlns="http://www.springframework.org/schema/security" 
      xmlns:beans="http://www.springframework.org/schema/beans" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> 
    <beans:bean id="authenticationManager" class="my.package.security.AuthenticationManager" /> 

    <http disable-url-rewriting="true" authentication-manager-ref="authenticationManager"> 
     <!--<intercept-url pattern="/" access="ROLE_ANONYMOUS" />--> 
     <intercept-url pattern="/login*" access="ROLE_ANONYMOUS" /> 
     <intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" /> 
     <intercept-url pattern="/*" access="ROLE_USER" /> 
     <form-login login-processing-url="/authorize" login-page="/login" authentication-failure-url="/login-failed" /> 
     <logout logout-url="/logout" logout-success-url="/login" /> 
     <remember-me data-source-ref="dataSource" /> 
    </http> 
</beans:beans> 

my.package.SessionListener:

public class SessionListener implements HttpSessionListener 
{ 
    public void sessionCreated(HttpSessionEvent httpSessionEvent) 
    { 

    } 

    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) 
    { 
     UserJpaDao userDao = WebApplicationContextUtils.getWebApplicationContext(httpSessionEvent.getSession().getServletContext()).getBean(UserJpaDao.class); 

     Authentication a = SecurityContextHolder.getContext().getAuthentication(); 
     if(a != null) 
     { 
      User loggedInUser = userDao.findByAlias(a.getName()); 

      if(loggedInUser != null) 
      { 
       loggedInUser.setOnline(false); 
       userDao.save(loggedInUser); 
      } 
     } 
    } 
} 

my.package.security.AuthenticationManager:

public class AuthenticationManager implements org.springframework.security.authentication.AuthenticationManager 
{ 
    @Autowired 
    UserJpaDao userDao; 

    public Authentication authenticate(Authentication authentication) throws AuthenticationException 
    { 
     User loggedInUser = null; 
     Collection<? extends GrantedAuthority> grantedAuthorities = null; 

     ... 

     loggedInUser = userDao.findByAlias(authentication.getName()); 
     if(loggedInUser != null) 
     { 
      // Verify password etc. 
      loggedInUser.setOnline(true); 
      userDao.save(loggedInUser); 
     } 
     else 
     { 
      loggedInUser = null; 
      throw new BadCredentialsException("Unknown username"); 
     } 

     return new UsernamePasswordAuthenticationToken(loggedInUser, authentication.getCredentials(), grantedAuthorities); 
    } 
} 

sessionCreatedsessionDestroyed被正確觸發,但SecurityContextHolder.getContext().getAuthentication();始終爲空。

更新:幾乎所有的工作都很完美。唯一的問題是當會話由於超時而到期時,SecurityContextHolder.getContext().getAuthentication()sessionDestroyed(...)方法中返回null。當註銷手動觸發時,它可以很好地工作。

有人可以幫助我嗎?任何提示都非常感謝。

謝謝

回答

1

我決定對會議登記方法(只因爲我沒能做出另一種方法工作)。這是我的代碼(重要部分)。

web.xml中:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
     version="2.5"> 

    <display-name>Test</display-name> 

    <context-param> 
     <param-name>contextConfigLocation</param-name> 
     <param-value> 
      /WEB-INF/applicationContext.xml 
      /WEB-INF/security.xml 
     </param-value> 
    </context-param> 

    ... 

    <!-- Security --> 
    <filter> 
     <filter-name>springSecurityFilterChain</filter-name> 
     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
    </filter> 

    <filter-mapping> 
     <filter-name>springSecurityFilterChain</filter-name> 
     <url-pattern>/*</url-pattern> 
    </filter-mapping> 

    <listener> 
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
    </listener> 

    <listener> 
     <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> 
    </listener> 

    <servlet> 
     <servlet-name>test</servlet-name> 
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
     <load-on-startup>1</load-on-startup> 
    </servlet> 

    <servlet-mapping> 
     <servlet-name>test</servlet-name> 
     <url-pattern>/</url-pattern> 
    </servlet-mapping> 

    <session-config> 
     <session-timeout>1</session-timeout> 
    </session-config> 
</web-app> 

的security.xml:

<beans:beans xmlns="http://www.springframework.org/schema/security" 
      xmlns:beans="http://www.springframework.org/schema/beans" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> 
    <beans:bean id="authenticationManager" class="my.package.security.AuthenticationManager" /> 
    <beans:bean id="userDetailsDao" class="my.package.dao.UserDetailsDao" /> 

    <http disable-url-rewriting="true" authentication-manager-ref="authenticationManager"> 
     <intercept-url pattern="/login*" access="ROLE_ANONYMOUS" /> 
     <intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" /> 
     <intercept-url pattern="/*" access="ROLE_USER" /> 
     <form-login login-processing-url="/authorize" login-page="/login" authentication-failure-url="/login-failed" /> 
     <logout logout-url="/logout" logout-success-url="/login" /> 
     <remember-me data-source-ref="dataSource" user-service-ref="userDetailsDao" /> 
     <session-management session-authentication-strategy-ref="sas" invalid-session-url="/invalid-session" /> 
    </http> 

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

    <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:bean> 
</beans:beans> 

my.package.security.AuthenticationManager:

public class AuthenticationManager implements org.springframework.security.authentication.AuthenticationManager 
{ 
    @Autowired 
    UserJpaDao userDao; 

    public Authentication authenticate(Authentication authentication) throws AuthenticationException 
    { 
     UserDetails userDetails = null; 

     if(authentication.getPrincipal() == null || authentication.getCredentials() == null) 
     { 
      throw new BadCredentialsException("Invalid username/password"); 
     } 

     User loggedInUser = userDao.findByAlias(authentication.getName()); 
     if(loggedInUser != null) 
     { 
      // TODO: check credentials 
      userDetails = new UserDetails(loggedInUser); 
     } 
     else 
     { 
      loggedInUser = null; 
      throw new BadCredentialsException("Unknown username"); 
     } 

     return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities()); 
    } 
} 

my.package.dao.UserDetailsDao(此僅用於記憶功能):

public class UserDetailsDao implements UserDetailsService 
{ 
    @Autowired 
    UserJpaDao userDao; 

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
    { 
     User user = userDao.findByAlias(username); 
     if(user != null) 
     { 
      return new UserDetails(user); 
     } 

     throw new UsernameNotFoundException("The specified user cannot be found"); 
    } 
} 

my.package.UserDetails:

public class UserDetails implements org.springframework.security.core.userdetails.UserDetails 
{ 
    private String alias; 
    private String encryptedPassword; 

    public UserDetails(User user) 
    { 
     this.alias = user.getAlias(); 
     this.encryptedPassword = user.getEncryptedPassword(); 
    } 

    @Override 
    public Collection<? extends GrantedAuthority> getAuthorities() 
    { 
     ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>(); 
     authorities.add(new SimpleGrantedAuthority("ROLE_USER")); 
     return authorities; 
    } 

    @Override 
    public String getPassword() 
    { 
     return this.encryptedPassword; 
    } 

    @Override 
    public String getUsername() 
    { 
     return this.alias; 
    } 

    @Override 
    public boolean isAccountNonExpired() 
    { 
     return true; 
    } 

    @Override 
    public boolean isAccountNonLocked() 
    { 
     return true; 
    } 

    @Override 
    public boolean isCredentialsNonExpired() 
    { 
     return true; 
    } 

    @Override 
    public boolean isEnabled() 
    { 
     return true; 
    } 
} 

sessionRegistry.getAllPrincipals()將返回List<Object> 「澆注」 到List<UserDetails>

我的代碼以獲取在線用戶,其中類型類User的目的是通過JPA持久保存在數據庫中的列表:

List<User> onlineUsers = userDao.findByListOfUserDetails((List<UserDetails>)(List<?>)sessionRegistry.getAllPrincipals()); 

注:sessionRegistry是自動裝配Autowired實現的類SessionRegistryImpl的。

注意:對於記憶功能,我使用持續標記方法。數據庫中需要persistent_logins(請參閱10.3 Persistent Token Approach)。

希望這可能對別人有用。

5

您不能使用SecurityContextHolder獲得您的SessionListener中的委託人,因爲該委託人僅在請求的上下文中有效。

所有您需要的信息是在會議本身

例子:

@Override 
public void sessionDestroyed(HttpSessionEvent se) { 
    HttpSession session = se.getSession(); 
    SecurityContext context = (SecurityContext)session.getAttribute("SPRING_SECURITY_CONTEXT"); 
    Authentication authentication = context.getAuthentication(); 
    Object principal = authentication.getPrincipal(); 

    // Your code goes here 

} 
+0

我可以的情況下,使用SecurityContextHolder的用戶點擊註銷鏈接,但在會話超時的情況下,只能這個解決方案。謝謝! (我需要它來協議註銷。) – 2015-11-18 10:10:22