spring security, how to expire all sessions of a u

2019-06-08 06:38发布

问题:

I have to solve the following scenario, in a Spring Security 3.2.5-RELEASE with Spring Core 4.1.2-RELEASE application running Java 1.7 on wildfly 8.1.

  1. user 'bob' logs in
  2. and Admin deletes 'bob'
  3. if 'bob' logs out, he can't log in. again but he`s current session remains active.
  4. i want to kick 'bob' out

    //this doesn't work
    for (final SessionInformation session :    sessionRegistry.getAllSessions(user, true)) {
             session.expireNow();
    }
    

回答1:

  1. add application event listener to track HttpSessionCreatedEvent and HttpSessionDestroyedEvent and register it as an ApplicationListener and maintain a cache of SessionId to HttoSession.
  2. (optional) add your own ApplicationEvent class AskToExpireSessionEvent -
  3. in you user management service add dependencies to SessionRegistry and ApplicationEventPublisher so that you could list through the currently active user sessions and find the ones (cause there could be many) which are active for the user you are looking for i.e. 'bob'
  4. when deleting a user dispatch an AskToExpireSessionEvent for each of his sessions.
  5. use a weak reference HashMap to track the sessions

user service:

     @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;
      }

     }

Application event:

     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 session caching listener:

     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());
                             }
                     }
             }

     }


回答2:

Using java config add the following code in your class extending WebSecurityConfigurerAdapter :

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

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

and add the following in your configure( HttpSecurity http ) method:

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

Also, set the registerSessionAuthenticationStratergy in your custom authentication bean as follows:

    usernamePasswordAuthenticationFilter
            .setSessionAuthenticationStrategy( registerSessionAuthStr( ) );

NOTE: Setting registerSessionAuthenticationStratergy in your custom authentication bean causes the prinicpal list to be populated and hence when you try to fetch the list of all prinicipals from sessionRegistry ( sessionRegistry.getAllPrinicpals() ), the list is NOT empty.