Java EE authentication: how to capture login event?

There’s no such event in Java EE. Yet. As part of JSR375, container managed security will be totally reworked as it’s currently scattered across different container implemantations and is not cross-container compatible. This is outlined in this Java EE 8 Security API presentation.

There’s already a reference implementation of Security API in progress, Soteria, developed by among others my fellow Arjan Tijms. With the new Security API, CDI will be used to fire authentication events which you can just @Observes. Discussion on the specification took place in this mailing list thread. It’s not yet concretely implemented in Soteria.

Until then, assuming FORM based authentication whereby the user principal is internally stored in the session, your best bet is manually checking in a servlet filter if there’s an user principal present in the request while your representation of the logged-in user is absent in the HTTP session.

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    HttpServletRequest request = (HttpServletRequest) req;
    String username = request.getRemoteUser();

    if (username != null && request.getSession().getAttribute("user") == null) {
        // First-time login. You can do your thing here.
        User user =  yourUserService.find(username);
        request.getSession().setAttribute("user", user);
    }

    chain.doFilter(req, res);
}

Do note that registering a filter on /j_security_check is not guaranteed to work as a decent container will handle it internally before the first filters are hit, for obvious security reasons (user-provided filters could manipulate the request in a bad way, either accidentally or awarely).

If you however happen to use a Java EE server uses the Undertow servletcontainer, such as WildFly, then there’s a more clean way to hook on its internal notification events and then fire custom CDI events. This is fleshed out in this blog of Arjan Tijms. As shown in the blog, you can ultimately end up with a CDI bean like this:

@SessionScoped
public class SessionAuthListener implements Serializable {

    private static final long serialVersionUID = 1L;

    public void onAuthenticated(@Observes AuthenticatedEvent event) {
        String username = event.getUserPrincipal().getName();
        // Do something with name, e.g. audit, 
        // load User instance into session, etc
    }

    public void onLoggedOut(@Observes LoggedOutEvent event) {
        // take some action, e.g. audit, null out User, etc
    }
}

Leave a Comment