0

My app handles logins with a @ViewScoped LoginBean, which is injected with a @SessionScoped SessionBean that stores user information and the current HttpSession. This app allows a user N separate sessions. After reaching that limit the user can only create another by killing off the oldest. This is done in the same LoginBean by asking the unmanaged UserSessionManager for the oldest SessionBean, and then invalidating its HttpSession.

Thus, logging in with session "A", we invalidate session "B". This all goes according to plan. But then, sometime during the remaining JSF phases, we also lose the SessionBean for session "A". Tracing down into the CDI code it appears that the session context for session "A" is being destroyed so when the redisplay finishes we have all new session beans.

We are using MyFaces 2.3.6, OpenWebBeans 2.0.16, OpenJDK 11

Is this a bug in OWB, or expected bahavior?

I'm also wondering if I have a fundamental misconception. If I save a SessionBean in my UserSessionManager and the retrieve it during a different session, should it retain its original state or does it get re-evaluated in the new SessionScoped context? I've been finding debugging difficult because my objects seem to actually be proxies, and the UI and debugger show different values at times.

Update 4/27/20: The @SessionScoped SessionBean is being destroyed by org.apache.webbeans.web.context.WebContextsService#destroyRequestContext() where it destroys the "PropagatedSessionContext". This PropagatedSessionContext is being set by WebContextsService#destroySessionContext(), which is designating the local session to be destroyed despite being given a different specific session. This is where I'm wondering if it's a bug in OWB.

Here's a simplified example of the code:

(In this test code I've made the SessionManager an @ApplicationScoped bean. In the original code it isn't, but the behavior is the same.)

@Named("loginbean")
@ViewScoped
public class LoginBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private String username;

    @Inject private ExternalContext externalContext;
    @Inject private SessionBean session;
    @Inject private SessionManager sessionMgr;

    public String killOldestDoLogin() {
        List<SessionInfo> sessions = sessionMgr.getSessions();
        SessionInfo oldest = sessions.get(0);
        sessionMgr.killSession(oldest.getSessionId());

        return doLogin();
    }


    public String doLogin() {
        username = username.trim();

        if (username != null && username.length() > 0) {
            // After a successful login, avoid session fixation attacks by
            // rotating the session ID.  This isn't strictly necessary as Faces has
            // its own session ID that a third party wouldn't have access to
            if (externalContext != null) {
                HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
                if (request != null && request.isRequestedSessionIdValid()) {

                    newSessionId   = request.changeSessionId();                    
                }
            }            

            HttpSession http = (HttpSession)externalContext.getSession(false);
            session.setUsername(username);
            session.setHttpSession(http);
            sessionMgr.addSession(http, session);            
        } 

        return "startPage.jsf");
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

.

@Named("sessionbean")
@SessionScoped
public class SessionBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private String username;
    private HttpSession httpSession;

    public void reset() {
        username = null;
        httpSession = null;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public HttpSession getHttpSession() {
        return httpSession;
    }

    public void setHttpSession(HttpSession session) {
        this.httpSession = session;
    }

    public String getSessionId() {
        return httpSession == null ? "null" : this.httpSession.getId();
    }

}

.

@Named("sessionmanager")
@ApplicationScoped
public class SessionManager {
    private HashMap<String,HttpSession> sessionMap = new HashMap<>();
    private HashMap<String,SessionBean> beanMap    = new HashMap<>();

    public void addSession(HttpSession http, SessionBean bean) {
        beanMap.put(http.getId(),  bean);
        sessionMap.put(http.getId(), http);
    }

    public boolean killSession(String sessionId) {
        HttpSession session = sessionMap.get(sessionId);
        sessionMap.remove(sessionId);
        beanMap.remove(sessionId);
        if (session != null) {
            session.invalidate();
        }
        return session != null;
    }

    public List<SessionInfo> getSessions() {
        List<SessionInfo> result = new ArrayList<>();
        for (String sessionId : sessionMap.keySet()) {
            SessionBean bean = beanMap.get(sessionId);
            HttpSession http = sessionMap.get(sessionId);

            SessionInfo info = new SessionInfo();
            info.setUsername(bean.getUsername());
            info.setSessionId(sessionId);
            info.setHttpSession(http));

            result.add(info);
        }

        return result;
    }
}

.

public class SessionInfo {
    private String      username;
    private String      sessionId;
    private HttpSession httpSession;

    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getSessionId() {
        return sessionId;
    }
    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }
    public HttpSession getHttpSession() {
        return httpSession;
    }
    public void setHttpSession(HttpSession httpSession) {
        this.httpSession = httpSession;
    }
}
Kukeltje
  • 12,223
  • 4
  • 24
  • 47
Didjit
  • 785
  • 2
  • 8
  • 26
  • BTW, the ```request.changeSessionId()``` doesn't impact the behavior, but commenting out just ```session.invalidate()``` avoids the problem. I've also ensured that the correct session is being invalidated. – Didjit Apr 24 '20 at 21:13
  • 1
    may be a duplicate of https://stackoverflow.com/questions/37114637/is-it-possible-to-invalidate-a-httpsession-from-another-session-using-session-id it's not a JSF/CDI issue, but a Servlet issue, seems you can't invalidate an HttpSession object if it's not the current session. – areus Apr 24 '20 at 21:53
  • And by tagging it myfaces and openwebbeans, you implicitly state it works in weld and mojarra or in a combination of weld and myfaces and mojarra and openwebbeans. Tags are not for what you use but for what is an implicit part of the problem. See [tagging]. Same is true for your title. See the same link! – Kukeltje Apr 25 '20 at 07:59
  • I think what you effectively want is a framework of keeping track of conversationscoped beans – Kukeltje Apr 25 '20 at 08:06
  • I don't think this bears on your problem, but a session bean should last as long as its session does, but after that the actual object is liable to be reused. – user207421 Apr 25 '20 at 08:15
  • Btw, how do you make sure the user can have multiple http sessions? Do you forecfully append the sessionId to the url instead of using cookies? – Kukeltje Apr 25 '20 at 09:16
  • @areus Thanks for the link. Didn't see that in my searches. I'll take a closer look. To invalidate another session it's simply HttpSession#invalidate(). It works but seems to have side-effects. – Didjit Apr 26 '20 at 15:09
  • Hi @Kukeltje. I tagged OWB because it was in that code that I saw the wrong context being destroyed. It was grabbing the context from the current session rather than the session found in the event. (I'll update with the actual file/methods). So I was thinking OWB might specifically be part of the problem rather than something more generic. – Didjit Apr 26 '20 at 15:21
  • @Kukeltje, A user could have multiple sessions only by using multiple browsers or incognito/privacy modes. (It's not a feature I'm thrilled with, but we're currently supporting it.) There's then a central UserSessionManager that maintains a hashmap of everyone's UserSession bean, which also points to their HttpSession. – Didjit Apr 26 '20 at 15:25
  • @Kukeltje, I've updated my question with details about the location in OWB that I find suspect. Basically, when invalidating *any* http session, it seems that the *current* session is being identified for destruction when the request session is destroyed. – Didjit Apr 27 '20 at 15:27

0 Answers0