This is a step-by-step HOW-TO configure AD server and Apache Tomcat server to achieve NTLM single sign-on. We will try to use Tomcat built-in SPNEGO support without 3rd party configuration. Advantage is, that it works out of box. Disadvantage is, that there’s no fallback to BASIC authentication if client doesn’t support SPNEGO autentication.
Environmental parameters
AD Server installation
Follow this AD step by step tutorial
Kerberos configuration
setspn -A HTTP/dc01.eu-central-1.compute.internal tomcat
ktpass /out c:\tomcat.keytab /mapuser tomcat@eu-central-1.compute.internal /princ HTTP/dc01.eu-central-1.compute.internal@EU-CENTRAL-1.COMPUTE.INTERNAL /pass <tomcat_user_password> /kvno 0 /pType KRB5_NT_PRINCIPAL
move C:\tomcat.keytab C:\programs\Tomcat8\conf
java -D”sun.security.krb5.debug”=true sun.security.krb5.internal.tools.Kinit -k -t C:\programs\Tomcat8\conf\tomcat.keytab HTTP/dc01.eu-central-1.compute.internal@EU-CENTRAL-1.COMPUTE.INTERNAL
For simplification we will run the Apache Tomcat server on the AD domain controller (so we have only a single windows server instance running), however in production the Tomcat will run on different server (Windows or Linux) instance.
First – we will define the user realm, so authenticated users will be recognized and their roles loaded. Let’s define user realm in the conf/server.xml
<Realm
className="org.apache.catalina.realm.JNDIRealm"
connectionURL="ldap://dc01.eu-central-1.compute.internal:389"
authentication="simple"
referrals="follow"
connectionName="cn=tomcat,cn=Users,dc=eu-central-1,dc=compute,dc=internal"
connectionPassword="VerySecretPassword"
userSearch="(sAMAccountName={0})"
userBase="cn=Users,dc=eu-central-1,dc=compute,dc=internal"
userSubtree="true"
roleSearch="(member={0})"
roleName="cn"
roleSubtree="true"
roleBase="cn=Builtin,dc=eu-central-1,dc=compute,dc=internal" />
Validate the AD settings using Active Directory Service management console or simple JXplorer tool to see exact AD LDAP parameters. After this step you should be able to log in into the web applications using active directory username and password (using BASIC or FORM authentication so far)
Create ${CATALINA_HOME}/conf/krb5.ini file:
[libdefaults]
default_realm = EU-CENTRAL-1.COMPUTE.INTERNAL
default_keytab_name = FILE:c:\programs\Tomcat8\conf\tomcat.keytab
default_tkt_enctypes = rc4-hmac,aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
default_tgs_enctypes = rc4-hmac,aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
forwardable=true
[realms]
EU-CENTRAL-1.COMPUTE.INTERNAL = {
kdc = dc01.eu-central-1.compute.internal:88
}
[domain_realm]
eu-central-1.compute.internal= EU-CENTRAL-1.COMPUTE.INTERNAL
.eu-central-1.compute.internal= EU-CENTRAL-1.COMPUTE.INTERNAL
Note:
Security consideration:
In most of the examples and configuration samples we can find rc4-hmac support for default_tgs_enctypes and/or default_ktk_enctypes. Do not use it. RC4-HMAC is considered weak and is deprecated. Support for RC4-HMAC should be disabled on the AD server, but it is out of scope of this blog.
This encryption is used to encrypt the Negotiation HTTP headers, so I am ok to use it until proper HTTPS is used. Still – it is safer than fallback to the BASIC authentication and passing cleartext password (regardless encoded).
Create ${CATALINA_HOME}/conf/jaas.conf file:
com.sun.security.jgss.krb5.initiate {
com.sun.security.auth.module.Krb5LoginModule required
doNotPrompt=true
principal="HTTP/dc01.eu-central-1.compute.internal@eu-central-1.compute.internal"
useKeyTab=true
keyTab="/c:/programs/Tomcat8/conf/tomcat.keytab"
storeKey=true;
};
com.sun.security.jgss.krb5.accept {
com.sun.security.auth.module.Krb5LoginModule required
doNotPrompt=true
principal="HTTP/dc01.eu-central-1.compute.internal@eu-central-1.compute.internal"
useKeyTab=true
keyTab="/c:/programs/Tomcat8/conf/tomcat.keytab"
storeKey=true;
};
Note:
As a test application we can create a simple WAR web application with following files:
/index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>SPNEGO test</title>
</head>
<body>
<h1>Hello World!</h1>
<p>auth type: <%=request.getAuthType()%> </p>
<p>remote user: <%=request.getRemoteUser() %> </p>
<p>principal: <%=request.getUserPrincipal() %></p>
<p>name: <%= (request.getUserPrincipal()!=null)?request.getUserPrincipal().getName():"NO PRINCIPAL" %></p>
</body>
</html>
/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<security-constraint>
<display-name>all_auth</display-name>
<web-resource-collection>
<web-resource-name>all</web-resource-name>
<description/>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>Users</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>SPNEGO</auth-method>
<realm-name>EU-CENTRAL-1.COMPUTE.INTERNAL</realm-name>
</login-config>
<security-role>
<description>App user role</description>
<role-name>Users</role-name>
</security-role>
</web-app>
Note:
/META-INF/context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/spnego">
<!-- valve will be explicitly created when SPNEGO is used,
this is to declare additional attributes -->
<Valve className="org.apache.catalina.authenticator.SpnegoAuthenticator"
alwaysUseSession="true" cache="true" />
</Context>
I believe the SpnegoAuthenticator valve is added to the context automatically when SPNEGO authentication method is used, in this definition we specified additional settings.
By default (according our experiences) the NTLM is directly supported by Internet Explorer and Google Chrome. Mozilla Firefox needs additional configuration.
To achieve SSO we need following configuration:
NTLM is basically challenge-response authentication with data sent over HTTP headers. As best practice we advice to keep the headers confidential using HTTPS protocol. That could mitigate potential pass-the-hash attack.
Resources: