spring 安全,如何使用户的所有会话过期

spring security, how to expire all sessions of a user

我必须解决以下情况,在 Spring 安全性 3.2.5-RELEASESpring Core 4.1.2-RELEASE 应用程序 运行 Java 1.7 on wildfly 8.1 中。

  1. 用户'bob'登录
  2. 并且管理员删除了 'bob'
  3. 如果 'bob' 注销,他将无法再次登录,但他的当前会话仍保持活动状态。
  4. 我想把'bob'踢出去

    //this doesn't work
    for (final SessionInformation session :    sessionRegistry.getAllSessions(user, true)) {
             session.expireNow();
    }
    
  1. 添加应用程序事件侦听器以跟踪HttpSessionCreatedEventHttpSessionDestroyedEvent并将其注册为ApplicationListener并维护一个SessionId缓存到HttoSession。
  2. (可选)添加您自己的 ApplicationEvent class AskToExpireSessionEvent -
  3. 在您的用户管理服务中,将依赖项添加到 SessionRegistryApplicationEventPublisher 以便您可以列出当前活动的用户会话并找到那些(因为可能有很多)活动的您正在寻找的用户,即 'bob'
  4. 删除用户时,会为他的每个会话分派 AskToExpireSessionEvent
  5. 使用弱引用 HashMap 来跟踪会话

用户服务:

     @Service
     public class UserServiceImpl implements UserService {

      /** {@link SessionRegistry} does not exists in unit tests */
      @Autowired(required = false)
      private Set<SessionRegistry> sessionRegistries;


      @Autowired
      private ApplicationEventPublisher publisher;


     /**
      * destroys all active sessions.
      * @return <code>true</code> if any session was invalidated^
      * @throws IllegalArgumentException
      */
      @Override
      public boolean invalidateUserByUserName(final String userName) {
              if(null == StringUtils.trimToNull(userName)) {
                      throw new IllegalArgumentException("userName must not be null or empty");
              }
              boolean expieredAtLeastOneSession = false;
              for (final SessionRegistry sessionRegistry : safe(sessionRegistries)) {
                      findPrincipal: for (final Object principal : sessionRegistry.getAllPrincipals()) {
                              if(principal instanceof IAuthenticatedUser) {
                                      final IAuthenticatedUser user = (IAuthenticatedUser) principal;
                                      if(userName.equals(user.getUsername())) {
                                              for (final SessionInformation session : sessionRegistry.getAllSessions(user, true)) {
                                                      session.expireNow();
                                                      sessionRegistry.removeSessionInformation(session.getSessionId());
                                                      publisher.publishEvent(AskToExpireSessionEvent.of(session.getSessionId()));
                                                      expieredAtLeastOneSession = true;
                                              }
                                              break findPrincipal;
                                      }
                              } else {
                                      logger.warn("encountered a session for a none user object {} while invalidating '{}' " , principal, userName);
                              }
                      }
              }
              return expieredAtLeastOneSession;
      }

     }

申请事件:

     import org.springframework.context.ApplicationEvent;

     public class AskToExpireSessionEvent extends ApplicationEvent {

             private static final long serialVersionUID = -1915691753338712193L;

             public AskToExpireSessionEvent(final Object source) {
                     super(source);
             }

             @Override
             public String getSource() {
                     return (String)super.getSource();
             }


             public static AskToExpireSessionEvent of(final String sessionId) {
                     return new AskToExpireSessionEvent(sessionId);
             }
     }

http 会话缓存侦听器:

     import java.util.Map;
     import java.util.WeakHashMap;

     import javax.servlet.http.HttpSession;

     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.context.ApplicationListener;
     import org.springframework.security.web.session.HttpSessionCreatedEvent;
     import org.springframework.security.web.session.HttpSessionDestroyedEvent;
     import org.springframework.stereotype.Component;

     import com.cb4.base.service.event.AskToExpireSessionEvent;


     @Component
     public class HttpSessionCachingListener {

             private static final Logger logger = LoggerFactory.getLogger(HttpSessionCachingListener.class);

             private final Map<String, HttpSession> sessionCache = new WeakHashMap<>();

             void onHttpSessionCreatedEvent(final HttpSessionCreatedEvent event){
                     if (event != null && event.getSession() != null && event.getSession().getId() != null) {
                             sessionCache.put(event.getSession().getId(), event.getSession());
                     }
             }

             void onHttpSessionDestroyedEvent(final HttpSessionDestroyedEvent event){
                     if (event != null && event.getSession() != null && event.getSession().getId() != null){
                             sessionCache.remove(event.getSession().getId());
                     }
             }

             public void timeOutSession(final String sessionId){
                     if(sessionId != null){
                             final HttpSession httpSession = sessionCache.get(sessionId);
                             if(null != httpSession){
                                     logger.debug("invalidating session {} in 1 second", sessionId);
                                     httpSession.setMaxInactiveInterval(1);
                             }
                     }
             }

             @Component
             static class HttpSessionCreatedLisener implements ApplicationListener<HttpSessionCreatedEvent> {

                     @Autowired
                     HttpSessionCachingListener parent;

                     @Override
                     public void onApplicationEvent(final HttpSessionCreatedEvent event) {
                             parent.onHttpSessionCreatedEvent(event);
                     }
             }

             @Component
             static class HttpSessionDestroyedLisener implements ApplicationListener<HttpSessionDestroyedEvent> {

                     @Autowired
                     HttpSessionCachingListener parent;

                     @Override
                     public void onApplicationEvent(final HttpSessionDestroyedEvent event) {
                             parent.onHttpSessionDestroyedEvent(event);
                     }
             }

             @Component
             static class AskToTimeOutSessionLisener implements ApplicationListener<AskToExpireSessionEvent> {

                     @Autowired
                     HttpSessionCachingListener parent;

                     @Override
                     public void onApplicationEvent(final AskToExpireSessionEvent event) {
                             if(event != null){
                                     parent.timeOutSession(event.getSource());
                             }
                     }
             }

     }

使用 java 配置在 class 中添加以下代码扩展 WebSecurityConfigurerAdapter :

      @Bean
public SessionRegistry sessionRegistry( ) {
    SessionRegistry sessionRegistry = new SessionRegistryImpl( );
    return sessionRegistry;
}

@Bean
public RegisterSessionAuthenticationStrategy registerSessionAuthStr( ) {
    return new RegisterSessionAuthenticationStrategy( sessionRegistry( ) );
}

并在您的 configure( HttpSecurity http ) 方法中添加以下内容:

    http.sessionManagement( ).maximumSessions( -1 ).sessionRegistry( sessionRegistry( ) );
    http.sessionManagement( ).sessionFixation( ).migrateSession( )
            .sessionAuthenticationStrategy( registerSessionAuthStr( ) );

此外,在您的自定义身份验证 bean 中设置 registerSessionAuthenticationStrategy,如下所示:

    usernamePasswordAuthenticationFilter
            .setSessionAuthenticationStrategy( registerSessionAuthStr( ) );

注意:在您的自定义身份验证 bean 中设置 registerSessionAuthenticationStrategy 会导致填充主体列表,因此当您尝试从 sessionRegistry 获取所有主体的列表时(sessionRegistry.getAllPrinicpals() ),列表不为空。