Configuring Tomcat 7 Single Sign-on with SPNEGO (Kerberos & LDAP)
Trying to set up a Single Sign-on Tomcat 7 server? Here's how to do so, complete with a look at what SPNEGO is, authentication vs. authorization, and Single Sign-on basics.
Join the DZone community and get the full member experience.
Join For FreePART 1 In The Same Forest
Introduction
I’d like to start by explaining some of the terms that will be used throughout this article before going into the configuration details for setting up Single Sign-on on a Tomcat 7 server.
So What is SPNEGO?
SPNEGO stands for Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO). It is a mechanism by which an authenticating body negotiates with the authenticator what security protocol to use, for example Kerberos, NTLM, Digest, or Basic
What is the Difference Between Authentication and Authorization?
Authentication is the act of verifying a user is who they say they are. Whereas authorization is the act of verifying that the user has sufficient permissions to access the data they are requesting. The difference is quite important so I wanted to introduce it early on.
Tomcat
Single Sign-on in Tomcat is handled as a two step process. First authentication is handled by a valve component. A valve component is an element in the request processing chain. And then the authorization is handled by the Realm. The Realm is essentially a user database that contains a collection of usernames, groups and their associated roles.
Single Sign-on
When you logon to a computer you are authenticating with either that local machine or some central server. Under normal circumstances if you then try and connect to an internal web application, that requires you be a user with certain rights, you would be prompted to login. However in the case of single sign-on this should all be transparent.
Kerberos
The user logs into Windows, they are authenticated with the Key Distribution Centre (KDC) in the case of Windows this would be the Primary Domain Controller. The OS of the client receives a TGT token for the user. When they try to connect to the Tomcat server the authentication mechanism is negotiated and their token is passed to Tomcat who then verifies it with the KDC. Once they have been authenticated Tomcat then retrieves their roles from the LDAP server in the case of Windows the Active Directory and decides if they have access to the resource they have requested on the server.
The sequence diagram below shows the interactions that take place in more detail.
Both the user and the Tomcat server have their own TGT token (credential associated with their identity). In the sequence diagram you can see outlined the division of responsibility between the Tomcat valve and the Tomcat realm. The valve is taking care of the authentications and the realm is taking care of the authorizations. Some internal interactions have been excluded to reduce the complexity of the diagram in particular related to Pre-Authentication Data PADATA which is used as part of the key sharing mechanism.
Ticket Granting Ticket
Ticket Granting Ticket (TGT) is a small, encrypted identification file with a limited validity period. TGT exists so users don't have to enter their password every time they wish to connect to a kerberized service, or keep a copy of their password around. If the TGT is compromised, an attacker can only masquerade as a user until the ticket expires. When a user first authenticates to Kerberos, s/he talks to the Authentication Service on the KDC to get a TGT. After authentication, this file is granted to a user (e.g. Tomcat, end user) for data traffic protection by the key distribution center (KDC). The TGT file contains the session key, its expiration date, and the user's IP address, which protects the user from man-in-the-middle attacks. The TGT is used to obtain a service ticket from the Ticket Granting Service (TGS). The user is granted access to network services only after this service ticket is provided.
Ticket Granting Service
Ticket Granting Service (TGS) is a principal that can grant tickets to others. When the user wants to talk to a kerberized service, s/he uses the Ticket Granting Ticket to talk to the TGS (e.g. Tomcat to talk to LDAP). The TGS verifies the user's identity using the Ticket Granting Ticket, and issues a ticket for the desired service. In our case, Tomcat becomes a TGS, granting tickets between the Active Directory (AD) and end user, that is, Tomcat is able to piggyback requests of the end user to the AD.
Service Principal and User Principal
The User Principal is the fully qualified username of a user from a particular domain. The server principal is essentially the same thing but is for a computer instead of a user and includes the protocol for which it is valid.
The end user (User Principal or username e.g., user@MYDOMAIN.COM) and Tomcat (Service Principal or username e.g., HTTP/server.mydomain.com@MYDOMAIN.COM) are domain/realm users that have user tokens (TGT) in their local ticket cache.
LDAP
Entries in LDAP are hierarchical and have each entry has a Distinguished Name (DN) that reflects that hierarchy. For example the DN for a user might be CN=Brett Crawley, OU=Development, DC=mydomain, DC=com where CN stands for common name, OU stands for organizational unit and DC stands for domain component. There will be one DC for each component of the domain.
In many organizations Users are split up by organization unit within LDAP and in some LDAP repositories this happens at the root of the tree. This can cause problems when searching for users because it is necessary to state the entry point at which to start searching. This entry point can only be one and therefore if your LDAP repository is organized in this manner it is necessary to user what is known as the Global Catalogue.
NOTE: This requires binding to be carried out at the root and it is necessary to use a different port to connect to the LDAP server
Configuration Steps
- Create a technical user for Tomcat in LDAP.
- Create your user groups in LDAP.
- Ensure that forward and reverse DNS resolutions are correctly configured for all servers involved.
- Associate a Service Principal Name (SPN) on the domain controller with the technical user, that is, associate 4 principals:
- HTTP/ FQDN @ REALM
- HTTP/ FQDN
- HTTP/ HOSTNAME @ REALM
- HTTP / HOSTNAME
- setspn -a PRINCIPALTECHNICAL_USER
- On the Domain Controller and on the Tomcat Server (if Windows), open the local security policy administration tool
- Go to “> local policies > security options”
- Look for “Network Security: Configure encryption types allowed for Kerberos”
RC4_HMAC
AES128
AES256
Future Encryption Types
On the Domain Controller, create a keytab file with the following command. The path is where the tomcat.keytab is written, and is a temporary location because the keytab is copied to the host machines:
- In the Active directory, open the technical user properties and go to the account tab. Flag the following values:
- Password never expires = true
- User cannot change password = true
- This account supports Kerberos AES128
- This account supports Kerberos AES256
- Use Kerberos DES encryption types for this account = should preferably be false
- The other settings are at your discretion as the are not pertinent to this setup.
- Still in the technical user properties, go to the delegation tab (which appeared as a consequence of step 6) and set the following value to true: Trust this user for delegation to any service (Kerberos only).
- Create the krb5.ini or krb5.conf file (depending on your platform) in the folder:
- Add the following Java startup property to the environment variables.
Create/edit the jaas.conf in the Tomcat conf (CATALINA_BASE/conf) or you could set the following property and point to a different file such as login.conf as is often referred to in other Kerberos documentation:
- Update the Java security libraries (Java Cryptography Extension (JCE) Unlimited Strength) to those for Strong Encryption. These libraries can be downloaded here:
Set the registry settings on host:
- Configure environment variables JAVA_OPTS, CATALINA_HOME and CATALINA_OPTS. In Windows, this can be done here: On Linux, this can be modified in the file:
- Enable Kerberos authentication in the browser.
- Modify the login-conf section of your web.xml file to use the SPNEGO auth method.
Then modify your security constraint sections of the web.xml, setting the auth-constraint roles to match the ones you specified in step 2. as shown below.
- Modify the server.xml to use the SPNEGO valve and the JNDI Realm.
If the value is not defined, this means that all types are enabled, contrary to what is written.
However, if some value is set, either retain or add
If you inadvertently set this value, you can remove it by going into the registry and deleting the value in the following hive of the registry:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters
ktpass /out PATH/tomcat.keytab
/mapuser TECHNICAL_USER@REALM
/princ HTTP/FQDN@REALM
/Pass PASSWORD
/crypto AES256-SHA1 ptype KRB5_NT_PRINCIPAL
Now copy the keytab file to the Tomcat server(s) into the /conf folder
CATALINA_BASE /conf
Alternatively, you can point to another file containing this configuration by setting the property:
-Djava.security.krb5.conf=PATH_TO_KRB_CONF
The contents of the file should look like the following:
[libdefaults]
default_realm=REALM
default_keytab_name=“CATALINA_BASE/conf/tomcat.keytab"
default_txt_enctypes=aes256-cts-hmac-shal-96,aes128-cts-hmac-shal-96
default_tgs_enctypes=aes256-cts-hmac-shal-96,aes128-cts-hmac-shal-96
forwardable=true
[realms]
REALM={
kdc=DOMAIN_CONTROLLER_FQDN:88
}
[domain_realm]
yourdomain.com=REALM
.yourdomain.com=REALM
Note: Make sure there are no spaces between forwardable, equals and true.
Redundancy: In krb5.init, add multiple KDCs and, in case of a load-balanced set-up, define the load balancer KDC as the master_kdc and the admin_server (defines the host, which is typically the master_kdc):
[realms]
REALM={
kdc = DOMAIN_CONTROLLER_FQDN_1:88
kdc = DOMAIN_CONTROLLER_FQDN_2:88
master_kdc = DOMAIN_CONTROLLER_FQDN_LoadBalancer:88
admin_server = DOMAIN_CONTROLLER_FQDN_LoadBalancer
}
-Djavax.security.auth.useSubjectCredsOnly=false
-Djava.security.auth.login.conf=PATH_TO_LOGIN_CONF
The content of the file (jaas.conf/login.conf) must include the accept and initiate sections as in the example below.
com.sun.security.jgss.krb5.accept {
com.sun.security.auth.module.Krb5LoginModule
required
doNotPrompt=true
principal="HTTP/FQDN@REALM"
keyTab="DATA_HOME/conf/tomcat.keytab"
storeKey=true
useKeyTab=true
useTicketCache=true
isInitiator=true
refreshKrb5Config=true
moduleBanner=true
storePass=true;
};
com.sun.security.jgss.krb5.initiate {
com.sun.security.auth.module.Krb5LoginModule
required
doNotPrompt=true
principal="HTTP/FQDN@REALM"
keyTab="DATA_HOME/conf/tomcat.keytab"
storeKey=true
useKeyTab=true
useTicketCache=true
isInitiator=true
refreshKrb5Config=true
moduleBanner=true
storePass=true
debug=true;
};
Java 6
http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
Java 7
http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
Java 8
http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
Put them in jre/lib/security and jdk/jre/lib/security
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters
Value Name: allowtgtsessionkey
Value Type: reg_sz or reg_dword
Value: 1
/etc/sysconfig/tomcat
In Internet Explorer, you can find this setting in the Internet options menu:
In Firefox, you must type “about:config” as the address, and then click on the button that says “I’ll be careful, I promise!”. Once you have access to the settings, type “neg” in the search field and edit the fields highlighted below:
<login-config>
<auth-method>SPNEGO</auth-method>
</login-config>
Add security roles for the roles you added to LDAP in step 2.
<security-role>
<description>Users</description>
<role-name>WebAppUsers</role-name>
</security-role>
<security-role>
<description>Admins</description>
<role-name>WebAppAdmins</role-name>
</security-role>
<security-constraint>
<web-resource-collection>
<web-resource-name>Common Area</web-resource-name>
<url-pattern>/common/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>PUT</http-method>
<http-method>HEAD</http-method>
<http-method>TRACE</http-method>
<http-method>DELETE</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>WebAppUser</role-name>
<role-name>WebAppAdmin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>User Area</web-resource-name>
<url-pattern>/user/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>PUT</http-method>
<http-method>HEAD</http-method>
<http-method>TRACE</http-method>
<http-method>DELETE</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>WebAppUser</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/admin/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>PUT</http-method>
<http-method>HEAD</http-method>
<http-method>TRACE</http-method>
<http-method>DELETE</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>WebAppAdmin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
NOTE: In the JNDI realm you should not include either the username or password as they will be ignored when using SPNEGO as the implementation of the realm does not support this configuration. It is assumed you will be using the GSSAPI
NOTE: Do not use the userPattern because this also is not supported by Tomcat when using SPNEGO
Replace property values as required.
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="off"/>
<Listener className="org.apache.catalina.core.JasperListener"/>
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
<Service name="Catalina">
<Connector port="8080" maxSavePostSize="2097152" URIEncoding="UTF-8"
maxHttpHeaderSize="65536"/>
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.JNDIRealm"
connectionURL="ldap://dc.mydomain.com:3268"
userSubtree="true"
userBase="cn=Users,dc=mydomain,dc=com"
userSearch="(sAMAccountName={0})"
userRoleName="memberOf"
roleBase="cn=Users,dc=mydomain,dc=com"
roleName="cn"
roleSearch="(member={0})"
roleSubtree="true"
roleNested="true"/>
<Host name="localhost" appBase="webapps">
<Context docBase="ROOT.war" path="">
<Valve className="org.apache.catalina.authenticator.SpnegoAuthenticator"
storeDelegatedCredential="true" />
</Context>
</Host>
</Engine>
</Service>
</Server>
Testing your Kerberos Config
kinit
On Windows, copy your krb5.ini to the Windows directory. On Linux, copy your krb5.conf file to the /etc folder.
kinit -k -t PATH_TO_KEYTAB SERVICE_PRINCIPAL (HTTP/ FQDN @ REALM )
kinit is for testing that krb5 config is correct.
kinit.exe in $JAVA_HOME/bin
Troubleshooting
BAD RESPONSE
Issues related to Bad Response could be caused by packet length restrictions. This is related to the token and ticket lengths in the HTTP headers.
There are three possible solutions to this:
- Add the maxHttpHeaderSize attribute to the connector in the server.xml with the value of 65535
- Run the following command on the servers and the machines suffering this problem. she
- Add the following to the krb5 config file in the [libdefaults] section so that TCP is used instead of UDP:
reg add “HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters” /v “MaxPacketSize” /t REG_DWORD /d 1
udp_preference_limit=1
Additional Debug Parameters
You can add the following Java property to increase the debug information related to Kerberos:
-Dsun.security.krb5.debug=true
For additional debugging in Apache, you can add the following lines to the logging.properties in the conf directory of Tomcat:
org.apache.catalina.realm.level = ALL
org.apache.catalina.realm.useParentHandlers = true
org.apache.catalina.authenticator.level = ALL
org.apache.catalina.authenticator.useParentHandlers = true
org.apache.juli.logging.UserDataHelper.CONFIG = INFO_ALL
org.apache.coyote.http11.level = DEBUG
For additional logging from the Krb5LoginModule, you can add the following lines to each section of the login.conf
debug=true
moduleBanner=true
Cleanup
In some cases, it may be necessary to empty the ticket cache because it contains tickets that are no longer valid (for example, if you have created a new keytab).
Clearing the ticket cache in Windows:
klist purge
Clearing the ticket cache in Linux:
kdestroy
Resources
Oracle About Kerberos Authentication
Tomcat Windows Authentication How-To
The Valve Component - SPNEGO Valve
Realm Configuration HOW-TO - JNDI Realm
The Realm Component - JNDI Directory Realm
Common Kerberos Error Messages (A-M)
Common Kerberos Error Messages (N-Z)
Troubleshooting Kerberos Errors
Troubleshooting Kerberos Delegation
Enabling Strict KDC Validation in Windows Kerberos
Next Week
Next week I will cover in depth accessing the LDAP server and how to configure cross forest authentication and the algorithm necessary to implement cross forest authorization when the root domain components of two trusted domains are different e.g. you have mydomain.com and mydomain.info.
Opinions expressed by DZone contributors are their own.
Comments