Bridging Undertow's authentication events to CDI

Undertow's native security system has an incredible useful feature that's painfully missing in the security system of Java EE; authentication events.

While Java EE applications could directly use the Undertow events, it's not directly clear how to do this. Furthermore having Undertow specific dependencies sprinkled throughout the code of an otherwise general Java EE application is perhaps not entirely optimal.

The following code shows how the Undertow dependencies can be centralized to a single drop-in jar, by creating an Undertow extension (handler) that bridges the native Undertow events to standard CDI ones. Upon adding such jar to a Java EE application, the application code only has to know about general CDI events.

First create the handler itself:

import io.undertow.security.api.NotificationReceiver;
import io.undertow.security.api.SecurityNotification;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import javax.enterprise.inject.spi.CDI;
import org.omnifaces.security.events.AuthenticatedEvent;
import org.omnifaces.security.events.LoggedOutEvent;

public final class AuthEventHandler implements HttpHandler {
    
    private final HttpHandler next;
    private static final SecurityNotificationReceiver NOTIFICATION_RECEIVER = new SecurityNotificationReceiver();

    public AuthEventHandler(final HttpHandler next) {
        this.next = next;
    }
    
    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        exchange.getSecurityContext().registerNotificationReceiver(NOTIFICATION_RECEIVER);    
        next.handleRequest(exchange);
    }
    
    private static class SecurityNotificationReceiver implements NotificationReceiver {

        @Override
        public void handleNotification(final SecurityNotification notification) {
            
            switch (notification.getEventType()) {
                case AUTHENTICATED:
                    CDI.current().getBeanManager().fireEvent(new AuthenticatedEvent(notification, notification.getAccount().getPrincipal()));
                    break;
                case LOGGED_OUT:
                    CDI.current().getBeanManager().fireEvent(new LoggedOutEvent(notification, notification.getAccount().getPrincipal()));
                    break;
                default:
                    break;
            }
        }
    }
}

Note that the AuthenticatedEvent and LoggedOutEvent types come from OmniSecurity, but they are just used for the example. As the types contain no required logic, any simple type could be used..

Next register the handler in an extension as follows:

import io.undertow.servlet.ServletExtension;
import io.undertow.servlet.api.DeploymentInfo;
import javax.servlet.ServletContext;

public class UndertowHandlerExtension implements ServletExtension {
    @Override
    public void handleDeployment(final DeploymentInfo deploymentInfo, final ServletContext servletContext) {
        deploymentInfo
           .addInnerHandlerChainWrapper(handler -> new AuthEventHandler(handler));
    }
}

Finally register the extension by adding its fully qualified class name to the file /META-INF/services/io.undertow.servlet.ServletExtension.

Now jar the result up and add that jar to a Java EE application. In such application, the two authentication events shown in the source above can now be observed as follows:

@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
    }
}

Experimenting with the above code proved that it indeed worked and it appears to be incredibly useful. Unfortunately this is now all specific to Undertow and thus only usable there and in servers that use Undertow (e.g. JBoss). It would be a real step forward for security in Java EE if it would support these simple but highly effective authentication events using a standardized API.

Arjan Tijms

Comments

Popular posts from this blog

Implementing container authentication in Java EE with JASPIC

Jakarta EE Survey 2022

Counting the rows returned from a JPA query