Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., CTO at artsy.net, fun at playplay.io, NYC

Email Twitter LinkedIn Github Strava


Most Tomcat users begin by implement Form-based authentication. Those deploying applications into enterprises soon discover that those enterprises use an Active Directory and have single sign-on on all intranet sites. They eventually find Waffle, but don’t want to take the ability to do form-based logon away.

How do we give users a way to logon either way?

You can accomplish this with the Waffle MixedAuthenticator.

Configure Tomcat

Download and Copy Files

Download Waffle 1.3 and copy waffle-jna.jar, jna.jar and platform.jar to Tomcat’s lib directory.

Configure Mixed Authenticator Valve

Add a valve and a realm to the application context. For an application, modify META-INF\context.xml.

<?xml version='1.0' encoding='utf-8'?>
  <Valve className="waffle.apache.MixedAuthenticator" principalFormat="fqn" roleFormat="both" />
  <Realm className="waffle.apache.WindowsRealm" />

Security Roles and Constraints

Configure security roles in WEB-INF\web.xml. The Waffle Mixed Authenticator adds all user’s security groups (including nested and domain groups) as roles during authentication.


Restrict access to website resources.

  <display-name>Waffle Security Constraint</display-name>
    <web-resource-name>Protected Area</web-resource-name>

Add a second security constraint that leaves the login page unprotected.

  <display-name>Login Page</display-name>
    <web-resource-name>Unprotected Login Page</web-resource-name>

Configure Form Login

Configure Form Login parameters with the location of the login page (repeated from the security constraint above) and an error page for failed logins. Modify WEB-INF\web.xml.


Login Page

Create a login page based on the following code. There’re two requirements for the login form. The form-based authentication must post to any valid location with the j_security_check parameter. The destination page will be loaded after a successful login. The single sign-on form must similarly post to any valid location with the j_negotiate_check parameter in the query string.

Here’s a rudimentary example that lands an authenticated user on index.jsp.

<form method="POST" name="loginform" action="index.jsp?j_security_check">
    <table style="vertical-align: middle;">
            <td><input type="text" name="j_username" /></td>
            <td><input type="password" name="j_password" /></td>
            <td><input type="submit" value="Login" /></td>
    <form method="POST" name="loginform" action="index.jsp?j_negotiate_check">
    <input type="submit" value="Login w/ Current Windows Credentials" />


A demo application can be found in the Waffle distribution in the Samples\Tomcat\waffle-mixed directory. Copy the entire directory into Tomcat’s webapps directory and navigate to http://localhost:8080/waffle-mixed. Pick your method of login.

How does it Work?

Implementation details follow. Read at your own risk.

From the unauthenticated login page we are making two possible requests: one will trigger Single Sign-On and another will trigger form-based authentication. To do single sign-on we will need access to the request/response objects and to do forms authentication we will need access to the realms interface. The place where we have both is in org.apache.catalina.Authenticator.

protected boolean authenticate(Request request, Response response, LoginConfig loginConfig) {

    String queryString = request.getQueryString();
    boolean negotiateCheck = (queryString != null && queryString.equals("j_negotiate_check"));
    boolean securityCheck = (queryString != null && queryString.equals("j_security_check"));

    Principal principal = request.getUserPrincipal();

    AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
    boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();

    if (principal != null && ! ntlmPost) {
        return true;
    } else if (negotiateCheck) {
        if (! authorizationHeader.isNull()) {
            return negotiate(request, response, authorizationHeader);
        } else {
            return false;
    } else if (securityCheck) {
        boolean postResult = post(request, response, loginConfig);
        if (postResult) {
            redirectTo(request, response, request.getServletPath());
        } else {
            redirectTo(request, response, loginConfig.getErrorPage());
        return postResult;
    } else {
        redirectTo(request, response, loginConfig.getLoginPage());
        return false;

Negotiate mimics the behavior of NegotiateAuthenticator, while form post follows the standard Authenticator registration process.

private boolean post(Request request, Response response, LoginConfig loginConfig) {
    String username = request.getParameter("j_username");
    String password = request.getParameter("j_password");
    IWindowsIdentity windowsIdentity = null;
    try {
        windowsIdentity = _auth.logonUser(username, password);
    } catch (Exception e) {
        return false;

    WindowsPrincipal windowsPrincipal = new WindowsPrincipal(windowsIdentity, context.getRealm(), _principalFormat, _roleFormat);
    register(request, response, windowsPrincipal, "FORM", windowsPrincipal.getName(), null);
    return true;