Removing the previous session, if it is already active (if a user forgot to log out) using JAAS

2.2k Views Asked by At

Let the following class be a session scoped CDI managed bean.

@Named
@SessionScoped
public class SessionUtils implements Serializable
{
    private Map<String, Object>sessionMap;
    private static final long serialVersionUID=1l;

    public SessionUtils() {}

    @PostConstruct
    private void init() {
        sessionMap=new HashMap<String, Object>();
    }

    public Map<String, Object> getSessionMap() {
        return sessionMap;
    }
}

The map is used to store user specific information.


I am using JAAS for authentication. There is a login Filter. Its skeleton looks like the following.

@WebFilter(filterName = "SecurityCheck", urlPatterns = {"/WEB-INF/jaas/*"}, dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD})
public final class SecurityCheck implements Filter
{
    @Inject
    private UserBeanLocal userService;
    @Inject
    private SessionUtils sessionUtils;

    public SecurityCheck() {}

    private void doBeforeProcessing(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String userName = request.getParameter("userName");
        request.login(userName != null ? userName.trim() : "", request.getParameter("password"));
    }

    private void doAfterProcessing(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        Map<String, Object> sessionMap = sessionUtils.getSessionMap();

        if (request.isUserInRole("ROLE_ADMIN")) {
            UserTable userTable = userService.setLastLogin(request.getParameter("userName"));
            userTable.setPassword(null);

            //sessionMap.put("user", userTable);
            request.getSession().setAttribute("newUser", new User());

            response.sendRedirect(request.getContextPath() + "/admin_side/Home.xhtml");
        }
        //else {Repeat for other authorities}
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String userName = httpServletRequest.getParameter("userName");
        HttpServletResponse httpServletResponse = ((HttpServletResponse) response);

        try {
            doBeforeProcessing(httpServletRequest, httpServletResponse);
        } catch (ServletException e) {
            httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/utility/LoginError.xhtml");
            return;
        }

        chain.doFilter(request, response);

        doAfterProcessing(httpServletRequest, httpServletResponse);
    }
}

I need to invoke the following HttpSessionBindingListener

public final class User implements HttpSessionBindingListener {
    private static final Map<UserTable, HttpSession> logins = new HashMap<UserTable, HttpSession>();

    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        System.out.println("valueBound() called.");
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        System.out.println("valueUnbound() called.");           
    }
}

The overridden methods are only invoked, when using the code along the following line.

request.getSession().setAttribute("newUser", new User()); //Or remove

But they are not invoked as obvious, if an instance of User is simply stored into the session map (in that CDI bean).

sessionMap.put("newUser", new User());

Are there other ways round in JAAS/CDI to deal with the same - to simulate HttpSessionBindingListener?

What I want to do is : if a user forgot to log out, the (previous still active) session should be removed in his/her next attempt to login.


One additional thing : UserTable (not User - it is just an example.) is an actual JPA entity class. The HttpSessionBindingListener needs to be implemented on that JPA entity which in turn requires an additional dependency on the service layer from the javax.servlet package unnecessarily increasing coupling between modules.

Can this be isolated so that the HttpSessionBindingListener can be implemented on the web layer (regardless of that JPA entity class - UserTable and still serving for that class i.e when an instance of UserTable is put into the session, the valueBound() method is called... and valueUnbound(), when an instance of UserTable is removed from HttpSession, replaced by another session attribute or the session itself is destroyed/invalidated)? I expect some ways in advanced Java EE.

The question title is not so meaningful as it should be. I will edit it later, when I envision a more meaningful title or you may voluntarily edit it before that time, if you like.

2

There are 2 best solutions below

2
On

As far as I understood, question is following, why HttpSessionBindingListener is called only when you call request.getSession()?

Session is became created only when you call request.getSession() and therefore listener is invoked. So you need to call that method in order to start a session, or it will be started if you call that later on during request.

By the way, it is bad practice to store HttpSession in static variable, it may lead to memory leak, if you will "forget" to remove it.

In order to achieve what you're trying to do, I would store only a static set of request.getSession().getId() in session listener. And in the filter, I would check if the set has such id, so you will get an existing session or create a new one. Or still store in map, if you really need to know to which user a session belongs.

1
On

if a user forgot to log out, the (previous still active) session should be removed in his/her next attempt to login.

What I usually do is to simply discard the "current" session when rendering the login page:

request.getSession().invalidate();

In other words going to the login page implies logging out of your current session.