2012-02-09 82 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