4

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.

Tiny
  • 27,221
  • 105
  • 339
  • 599
  • I would use `HttpSessionAttributeListener` instead since that will be invoked when you set `User` as an attribute, not just when the session is created. Then use an `@ApplicationScoped` bean to store the `UserId` to `SessionId` mappings. A word of caution, if you ever need to scale beyond a single server, you'll need some sort of external store to handle the `UserId` / `SessionId` map – Baldy Dec 16 '14 at 13:10
  • But that requires `HttpSessionAttributeListener` to be implemented on the entity class associated with users (`User`). The entity class is available on the service layer which in turn requires an extra dependency from the `javax.servlet` API which is incorrect and increases coupling between modules - EJBs on the service layer are not necessarily local (as marked by `@Local`). They can be remote EJBs deployed on a separate server that could acquire the `javax.servlet` package only if a separate library was added. `javax.servlet` is a web layer thing which the service layer should be unaware of. – Tiny Dec 16 '14 at 17:40
  • There is no need to implement the listener class on your entity class. `HttpSessionAttributeListener`, properly annotated with `@WebListener` is stand-alone on the web layer and is simply listening for when a `User` (or whatever class) object is added to or removed from the session map. Separation is easy to maintain in this case. Using remote EJBs for session management certainly can be done but it will be complex and expensive operationally. Using something like memcached would be far simpler and more scalable. – Baldy Dec 16 '14 at 17:49

2 Answers2

0

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.

win_wave
  • 1,498
  • 11
  • 9
  • Actually, the overridden method `valueBound()` of `HttpSessionBindingListener` is invoked, when an instance of its implementation is set to a session as an attribute and the `valueUnbound()` method is invoked, when an instance of its implementation is removed from session, replaced by another attribute or the session itself is destroyed/invalidated. The session is actually already created (when the first request is sent by the client). – Tiny Dec 09 '14 at 21:41
  • I was looking for a way to invoke these methods without having to expose an `HttpSession` like `request.getSession()` (because I am using CDI beans) and to remove an extra dependency of the `javax.servlet.*` API on the service layer , since entity classes are available on the service layer (the EJB module) where this interface `HttpSessionBindingListener` needs to be implemented. Having this dependency on that layer implies tight coupling between modules. Thanks anyway for dedicating time and regarding this question :) – Tiny Dec 09 '14 at 21:48
0

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.

geert3
  • 7,086
  • 1
  • 33
  • 49
  • Before discarding the current user's session, it is to be verified that the user being logged in has already an active HTTP session that leads to maintaining a list of user sessions. The question implies this kind of things. For example, if you are working in an office several KMs away from your home and that while working in your office, you forgot to log out. Afterwards, if you logged in after getting back to your home, the previous session would be kept on dangling unnecessarily on the server util it is timed out by the server or you log out - perhaps the next day you go in your office. – Tiny Dec 16 '14 at 17:46