Uploaded image for project: 'Spring Security'
  1. Spring Security
  2. SEC-1499

SessionFixationProtectionStrategy reused destroyed Spring session bean as session attribute when migrateSessionAttributes is true

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Major
    • Resolution: Won't Fix
    • Affects Version/s: 3.0.2
    • Fix Version/s: 3.1.0.M1
    • Component/s: Web
    • Labels:
      None

      Description

      Scenario
      SessionFixationProtectionStrategy saves bean with the method createMigratedAttributeMap
      SessionFixationProtectionStrategy invalidate the session.
      The bean destroy's methods are called.
      The destroyed bean are injected into the new session

        Activity

        Hide
        luke Luke Taylor added a comment -

        I've added some docs and Javadoc to clarify that this may cause issues, but I think the best option is to implement a custom strategy which deals with the application's life-cycle requirements for specific beans.

        Show
        luke Luke Taylor added a comment - I've added some docs and Javadoc to clarify that this may cause issues, but I think the best option is to implement a custom strategy which deals with the application's life-cycle requirements for specific beans.
        Hide
        zgmnkv Alexander Zagumennikov added a comment -

        Hi, I faced with this issue too and I have some considerations about it.
        When migrating attributes from one session to another we can set some flag in session attributes that indicates that it's a migration, not an actual session descruction. And we can check this flag in DestructionCallbackBindingListener#valueUnbound and not call destructionCallback.

        Show
        zgmnkv Alexander Zagumennikov added a comment - Hi, I faced with this issue too and I have some considerations about it. When migrating attributes from one session to another we can set some flag in session attributes that indicates that it's a migration, not an actual session descruction. And we can check this flag in DestructionCallbackBindingListener#valueUnbound and not call destructionCallback.
        Hide
        zgmnkv Alexander Zagumennikov added a comment -

        If anyone interested, here's my solution:

        public class SessionMigratingSwitchUserFilter extends SwitchUserFilter {
         
            private static final String SAVED_SESSION_ATTRIBUTES_KEY = SessionMigratingSwitchUserFilter.class.getName() + ".SAVED_SESSION_ATTRIBUTES";
            private static final String SAVED_SESSION_ATTRIBUTES_DESTRUCTION_CALLBACK_KEY = SAVED_SESSION_ATTRIBUTES_KEY + ".DESTRUCTION_CALLBACK";
         
            @Override
            protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException {
                Authentication result = super.attemptSwitchUser(request);
         
                if (result == null) {
                    //switch user not successful
                    return result;
                }
         
                HttpSession session = request.getSession(false);
         
                if (session == null) {
                    return result;
                }
         
                synchronized (WebUtils.getSessionMutex(session)) {
                    SessionMigrationUtils.setSessionMigrating(session);
         
                    //save security context
                    Object sessionContextAttribute = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
                    session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
         
                    final Map<String, Object> sessionAttributesToSave = new HashMap<String, Object>();
                    Enumeration attrNames = session.getAttributeNames();
                    while (attrNames.hasMoreElements()) {
                        String attrName = (String) attrNames.nextElement();
         
                        if (SessionMigrationUtils.SESSION_MIGRATING_ATTRIBUTE_NAME.equals(attrName)) {
                            continue;
                        }
         
                        sessionAttributesToSave.put(attrName, session.getAttribute(attrName));
                        session.removeAttribute(attrName);
                    }
         
                    SessionMigrationUtils.unsetSessionMigrating(session);
         
                    session.invalidate();
         
                    //create new session
                    session = request.getSession();
         
                    SessionMigrationUtils.setSessionMigrating(session);
         
                    //set new session security context
                    session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sessionContextAttribute);
         
                    //set saved session attributes
                    session.setAttribute(SAVED_SESSION_ATTRIBUTES_KEY, sessionAttributesToSave);
         
                    //if attribute removed (in case of session invalidation), we should call valueUnbound callbacks of saved attributes
                    session.setAttribute(SAVED_SESSION_ATTRIBUTES_DESTRUCTION_CALLBACK_KEY, new HttpSessionBindingListener() {
                        public void valueBound(HttpSessionBindingEvent event) {
                        }
         
                        public void valueUnbound(HttpSessionBindingEvent event) {
                            if (SessionMigrationUtils.isSessionMigrating(event.getSession())) {
                                return;
                            }
                            for (Object savedSessionAttribute : sessionAttributesToSave.values()) {
                                if (savedSessionAttribute instanceof HttpSessionBindingListener) {
                                    ((HttpSessionBindingListener) savedSessionAttribute).valueUnbound(event);
                                }
                            }
                        }
                    });
         
                    SessionMigrationUtils.unsetSessionMigrating(session);
                }
         
                return result;
            }
         
            @Override
            protected Authentication attemptExitUser(HttpServletRequest request) throws AuthenticationCredentialsNotFoundException {
                Authentication result = super.attemptExitUser(request);
         
                if (result == null) {
                    //exit user not successful
                    return result;
                }
         
                HttpSession session = request.getSession(false);
         
                if (session == null) {
                    return result;
                }
         
                synchronized (WebUtils.getSessionMutex(session)) {
                    SessionMigrationUtils.setSessionMigrating(session);
         
                    //noinspection unchecked
                    Map<String, Object> sessionAttributesToRestore = (Map<String, Object>) session.getAttribute(SAVED_SESSION_ATTRIBUTES_KEY);
                    session.removeAttribute(SAVED_SESSION_ATTRIBUTES_DESTRUCTION_CALLBACK_KEY);
                    session.removeAttribute(SAVED_SESSION_ATTRIBUTES_KEY);
         
                    Object sessionContextAttribute = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
                    session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
         
                    SessionMigrationUtils.unsetSessionMigrating(session);
         
                    session.invalidate();
         
                    //create new session
                    session = request.getSession();
         
                    SessionMigrationUtils.setSessionMigrating(session);
         
                    if (sessionAttributesToRestore != null) {
                        //restore attributes
                        for (Map.Entry<String, Object> sessionAttributeToRestore : sessionAttributesToRestore.entrySet()) {
                            session.setAttribute(sessionAttributeToRestore.getKey(), sessionAttributeToRestore.getValue());
                        }
                    }
         
                    if (sessionContextAttribute != null) {
                        //restore security context
                        session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sessionContextAttribute);
                    }
         
                    SessionMigrationUtils.unsetSessionMigrating(session);
                }
         
                return result;
            }
        }

        public abstract class SessionMigrationUtils {
         
            public  static final String SESSION_MIGRATING_ATTRIBUTE_NAME = SessionMigrationUtils .class.getName() + ".MIGRATING";
         
            public static boolean isSessionValid(HttpSession session) {
                try {
                    session.getCreationTime();
                    return true;
                } catch (IllegalStateException e) {
                    return false;
                }
            }
         
            public static boolean isSessionMigrating(HttpSession session) {
                if (!isSessionValid(session)) {
                    return false;
                }
                Boolean migrating = (Boolean) session.getAttribute(SESSION_MIGRATING_ATTRIBUTE_NAME);
                return migrating != null && migrating;
            }
         
            public static void setSessionMigrating(HttpSession session) {
                session.setAttribute(SESSION_MIGRATING_ATTRIBUTE_NAME, true);
            }
         
            public static void unsetSessionMigrating(HttpSession session) {
                session.removeAttribute(SESSION_MIGRATING_ATTRIBUTE_NAME);
            }
        }
         

        Show
        zgmnkv Alexander Zagumennikov added a comment - If anyone interested, here's my solution: public class SessionMigratingSwitchUserFilter extends SwitchUserFilter {   private static final String SAVED_SESSION_ATTRIBUTES_KEY = SessionMigratingSwitchUserFilter. class .getName() + ".SAVED_SESSION_ATTRIBUTES" ; private static final String SAVED_SESSION_ATTRIBUTES_DESTRUCTION_CALLBACK_KEY = SAVED_SESSION_ATTRIBUTES_KEY + ".DESTRUCTION_CALLBACK" ;   @Override protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException { Authentication result = super .attemptSwitchUser(request);   if (result == null ) { //switch user not successful return result; }   HttpSession session = request.getSession( false );   if (session == null ) { return result; }   synchronized (WebUtils.getSessionMutex(session)) { SessionMigrationUtils.setSessionMigrating(session);   //save security context Object sessionContextAttribute = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);   final Map<String, Object> sessionAttributesToSave = new HashMap<String, Object>(); Enumeration attrNames = session.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement();   if (SessionMigrationUtils.SESSION_MIGRATING_ATTRIBUTE_NAME.equals(attrName)) { continue ; }   sessionAttributesToSave.put(attrName, session.getAttribute(attrName)); session.removeAttribute(attrName); }   SessionMigrationUtils.unsetSessionMigrating(session);   session.invalidate();   //create new session session = request.getSession();   SessionMigrationUtils.setSessionMigrating(session);   //set new session security context session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sessionContextAttribute);   //set saved session attributes session.setAttribute(SAVED_SESSION_ATTRIBUTES_KEY, sessionAttributesToSave);   //if attribute removed (in case of session invalidation), we should call valueUnbound callbacks of saved attributes session.setAttribute(SAVED_SESSION_ATTRIBUTES_DESTRUCTION_CALLBACK_KEY, new HttpSessionBindingListener() { public void valueBound(HttpSessionBindingEvent event) { }   public void valueUnbound(HttpSessionBindingEvent event) { if (SessionMigrationUtils.isSessionMigrating(event.getSession())) { return ; } for (Object savedSessionAttribute : sessionAttributesToSave.values()) { if (savedSessionAttribute instanceof HttpSessionBindingListener) { ((HttpSessionBindingListener) savedSessionAttribute).valueUnbound(event); } } } });   SessionMigrationUtils.unsetSessionMigrating(session); }   return result; }   @Override protected Authentication attemptExitUser(HttpServletRequest request) throws AuthenticationCredentialsNotFoundException { Authentication result = super .attemptExitUser(request);   if (result == null ) { //exit user not successful return result; }   HttpSession session = request.getSession( false );   if (session == null ) { return result; }   synchronized (WebUtils.getSessionMutex(session)) { SessionMigrationUtils.setSessionMigrating(session);   //noinspection unchecked Map<String, Object> sessionAttributesToRestore = (Map<String, Object>) session.getAttribute(SAVED_SESSION_ATTRIBUTES_KEY); session.removeAttribute(SAVED_SESSION_ATTRIBUTES_DESTRUCTION_CALLBACK_KEY); session.removeAttribute(SAVED_SESSION_ATTRIBUTES_KEY);   Object sessionContextAttribute = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);   SessionMigrationUtils.unsetSessionMigrating(session);   session.invalidate();   //create new session session = request.getSession();   SessionMigrationUtils.setSessionMigrating(session);   if (sessionAttributesToRestore != null ) { //restore attributes for (Map.Entry<String, Object> sessionAttributeToRestore : sessionAttributesToRestore.entrySet()) { session.setAttribute(sessionAttributeToRestore.getKey(), sessionAttributeToRestore.getValue()); } }   if (sessionContextAttribute != null ) { //restore security context session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sessionContextAttribute); }   SessionMigrationUtils.unsetSessionMigrating(session); }   return result; } } public abstract class SessionMigrationUtils {   public static final String SESSION_MIGRATING_ATTRIBUTE_NAME = SessionMigrationUtils . class .getName() + ".MIGRATING" ;   public static boolean isSessionValid(HttpSession session) { try { session.getCreationTime(); return true ; } catch (IllegalStateException e) { return false ; } }   public static boolean isSessionMigrating(HttpSession session) { if (!isSessionValid(session)) { return false ; } Boolean migrating = (Boolean) session.getAttribute(SESSION_MIGRATING_ATTRIBUTE_NAME); return migrating != null && migrating; }   public static void setSessionMigrating(HttpSession session) { session.setAttribute(SESSION_MIGRATING_ATTRIBUTE_NAME, true ); }   public static void unsetSessionMigrating(HttpSession session) { session.removeAttribute(SESSION_MIGRATING_ATTRIBUTE_NAME); } }  
        Hide
        zgmnkv Alexander Zagumennikov added a comment -

        oops, wrong issue

        Show
        zgmnkv Alexander Zagumennikov added a comment - oops, wrong issue
        Hide
        issuemaster Spring Issuemaster added a comment -
        Show
        issuemaster Spring Issuemaster added a comment - This issue has been migrated to https://github.com/spring-projects/spring-security/issues/1742

          People

          • Assignee:
            luke Luke Taylor
            Reporter:
            nithril Nicolas Labrot
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development