2

I´m working on a web application with Java EE 6 (JSF CDI EJB) which should not allow concurrent logins (same user and pass).

What I like is :

If a user logs in twice the first session needs to be invalidated and the old session data (incl. all CDI Beans with SessionScope or other scopes like WindowScope from Apache CODI ) gets transferred to the new session.

It's something like a wanted session hijacking approach :-)

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
urbiwanus
  • 713
  • 5
  • 21
  • Why don't you just hide the login page for already logged-in users and reject any login attempt with a message like *"You are already logged in. Please logout if you wish to login as a different user."* or something sensible like that. – BalusC Jul 16 '12 at 21:16
  • Its not that simple. because the user want to work on two different stations without caring on the login. He just want to see the same output and the same state of the application – urbiwanus Jul 16 '12 at 21:29
  • Then why do you need to invalidate the first session? – BalusC Jul 16 '12 at 21:52
  • The second session could change the data on the first. Image you have open an order on the first session then you change your workspace and you want to edit the order again on the second session but you forgot to logout ( no change to persist any data at this point). I dont want to invalidate the session just want to transfer all data to the newly created one – urbiwanus Jul 16 '12 at 22:04
  • MyFaces CODI beans are stored for a window and all windows in the session. If you replicate the content of the session, you replicate those beans as well (for all windows). – Dar Whi Jul 18 '12 at 20:45

3 Answers3

0

There is no inherent mechanism for this in Java EE 6.

As I can't imagine that you want to transfer something like an ongoing usecase (e.g. an open checkout-process) from one session to another, I suggest that you simply track the user's GUI state.

RESTful URLs sound like an ideal approach for this. Persist the last user URL / user action (e.g. www.myapp.com/orders/new/12) and reopen it on a new login.

If you don't want to persist this in the DB, an application-scoped map userid / url might be the KISS way.

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
Jan Groth
  • 14,039
  • 5
  • 40
  • 55
  • I´m not sure if this will work in all my use cases. But i will try it. – urbiwanus Jul 17 '12 at 08:20
  • Just out of curiosity - what kind of system is it that you are building? Why are users logging into into different stations in a relatively short period of time? – Jan Groth Jul 17 '12 at 10:48
  • Think of something like a POS system with different terminals. I can not go into detail . Im sorry – urbiwanus Jul 17 '12 at 18:07
0

You could use an stateless bean for the user, so every time one should try to log in / re-log in, the current session gets invalidated (at the beginning of the log in procedure)

consider this kind of approach :

try {           
   session = request.getSession();   //the request is passed by another page or action
   if(session.getAttribute("user") != null) {

           //your code to forward or handle the existing user (re-log in/ do nothing etc.)
}
Lucian Enache
  • 2,510
  • 5
  • 34
  • 59
0

I solved this issue with the help of a filter

public class SessionReplicationFilter implements Filter {

@Inject
SessionReplicationManager manager;

public SessionReplicationFilter() {
}


@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    //Process chain first 
    if (chain != null) {
        chain.doFilter(request, response);
    }

    //check http request
    if (request instanceof HttpServletRequest) {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // Retrieve the session and the principal (authenticated user)
        // The principal name is actually the username
        HttpSession session = httpRequest.getSession();
        Principal principal = httpRequest.getUserPrincipal();
        if (principal != null && principal.getName() != null && session != null) {
            manager.checkExistingSession(principal.getName(), session)) 
        }
    }

}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void destroy() {

}

}

The Manager looks following

@ApplicationScoped public class SessionReplicationManager {

private Map<String, HttpSession> map = new ConcurrentHashMap<String, HttpSession>();


public boolean checkExistingSession(String user, HttpSession session) {
    if (map.keySet().contains(user)) {
        if (!session.getId().equals(map.get(user).getId())) {
            System.out.println("User already logged in ");
            HttpSession oldSession = map.get(user);
            // copies all attributes from the old session to the new session (replicate the session)
            Enumeration<String> enumeration = oldSession.getAttributeNames();
            while (enumeration.hasMoreElements()) {
                String name = enumeration.nextElement();
                System.out.println("Chaning attribut " + name);
                session.setAttribute(name, oldSession.getAttribute(name));
            }
            // invalidates the old user session (this keeps one session per user)
            oldSession.invalidate();
            map.put(user, session);
            return true;
        }
    } else {
        System.out.println("Putting "+user+" into session cache");
        map.put(user, session);
        return false;
    }
    return false;
}

}

It works very well with CoDI ViewScoped annotated Beans

If the first user gets invalidated every (AJAX) request causes a session expired exception which could easily be handled even with a restore session button

Only minor problem with the viewscoped beans is, that they get a new view id. By changing them back to the origin one everything works fine.

Things I need to add :

  • Auto logout ( ajax polling, websockets,...)
  • Some kind of registry where all viewscoped-id are stored

Missing in this comment :

  • web.xml configuration

Regards

urbiwanus
  • 713
  • 5
  • 21
  • I hope that anyone who looks at your reply will realize that this is just an insane memory leak (HttpSessions map never gets cleared). – Radoslav H. Mar 10 '15 at 23:39
  • You are right. The HttpSessionListener , which observes sessionDestroyed event is missing here. The listener removes all invalid sessions from the map ( ie. timeout, logout ,...) – urbiwanus Mar 11 '15 at 07:25