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'?>
- <Context>
- <Valve className="waffle.apache.MixedAuthenticator" principalFormat="fqn" roleFormat="both" />
- <Realm className="waffle.apache.WindowsRealm" />
- </Context>
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.
- <security-role>
- <role-name>Everyone</role-name>
- </security-role>
Restrict access to website resources.
- <security-constraint>
- <display-name>Waffle Security Constraint</display-name>
- <web-resource-collection>
- <web-resource-name>Protected Area</web-resource-name>
- <url-pattern>/*</url-pattern>
- </web-resource-collection>
- <auth-constraint>
- <role-name>Everyone</role-name>
- </auth-constraint>
- </security-constraint>
Add a second security constraint that leaves the login page unprotected.
- <security-constraint>
- <display-name>Login Page</display-name>
- <web-resource-collection>
- <web-resource-name>Unprotected Login Page</web-resource-name>
- <url-pattern>/login.jsp</url-pattern>
- </web-resource-collection>
- </security-constraint>
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-config>
- <form-login-config>
- <form-login-page>/login.jsp</form-login-page>
- <form-error-page>/error.html</form-error-page>
- </form-login-config>
- </login-config>
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;">
- <tr>
- <td>Username:</td>
- <td><input type="text" name="j_username" /></td>
- </tr>
- <tr>
- <td>Password:</td>
- <td><input type="password" name="j_password" /></td>
- </tr>
- <tr>
- <td><input type="submit" value="Login" /></td>
- </tr>
- </table>
- </form>
- <hr>
- <form method="POST" name="loginform" action="index.jsp?j_negotiate_check">
- <input type="submit" value="Login w/ Current Windows Credentials" />
- </form>
Demo
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.
- @Override
- 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 {
- sendUnauthorized(response);
- 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;
- }
Links