To provide Single-SignOn (SSO) to your web service on a linux server
in a windows environment (Active Directory) you can add a login filter
to tomcat to accept Kerberos tokens. It's really quite simple, if you
set it up correctly, which is not simple to figure out.
|
Ack: Compiled from http://portlandlanguagecraft.com/ and https://pixabay.com/en/chain-gold-power-connection-rights-307886/ |
I did it using a custom SPNEGO filter to also extract AD groups from the kerberos tokens.
Mostly follow
https://tomcat.apache.org/tomcat-7.0-doc/windows-auth-howto.html but with many additional tweaks.
You will need to create/edit the following files (On Ubuntu):
/etc/tomcat7/web.xml
/etc/tomcat7/login.conf
/etc/tomcat7/krb5.conf
/etc/tomcat7/mykeytab.keytab
/usr/share/tomcat7/libs/spnego-r7.jar
(These files are also found in /var/lib/tomcat7/conf).
Step-by-step (First section just for login and second section for getting groups too):
AUTHENTICATION (Login):
Download the Spnego HTTP filter:
Available from
https://sourceforge.net/projects/spnego/. Put the file under /usr/share/tomcat7/libs/spnego-r7.jar to make tomcat load it on startup.
On the Active Directory (AD) / Kerberos Key Distribution Center (KDC) / Windows server:
Add service user:
Add a service user to let you linux server "log in" and validate kerberos tokens.
Link SPN to service user:
SPN
(Service Principal Names) are identifiers for users or hosts. We need
to add the ones representing our server. NOTE: The SPN is case sensitive
and you must use the same case everywhere. The command is on the form:
setspn.exe -A HTTP/<HOSTNAME> DOMAIN\<SERVICE USER>
So on your windows/AD server enter the following
setspn.exe -A
HTTP/myserver DOMAIN\myserviceuser
setspn.exe -A
HTTP/myserver.domain.local DOMAIN\myserviceuser
to link the SPN HTTP/myserver to the user myserviceuser.
Generate keytab:
The keytab is file which stores SPNs/usernames and password for them.
According
to the apache tutorial you should do this on your windos/AD server
using the ktpass tool. I found it was better to use the ktab.exe that
comes with java on windows:
ktab -a HTTP/<HOSTNAME> <SERVICE USER PASSWORD> -k <OUTPUT FILE> -n 0
e.g.
ktab -a HTTP/myserver.domain.local myservicepassword -k mykeytab.keytab -n 0
Note the n 0 flag which sets key version number, it needs to be 0 for tomcat/Spnego to find the key.
Put the mykeytab.keytab file under /etc/tomcat7/mykeytab.keytab on your linux server.
We
also need to make the computers in the windows network trust our
server, to do this we can use Group Policy on the AD server. But we'll
get back to that later.
On the linux server (as sudo):
Add a filter to /etc/tomcat7/web.xml:
<!-- ======================== SPNEGO filter ==============================-->
<filter>
<filter-name>SpnegoHttpFilter</filter-name>
<filter-class>net.sourceforge.spnego.SpnegoHttpFilter</filter-class>
<init-param>
<param-name>spnego.allow.basic</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>spnego.allow.localhost</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>spnego.allow.unsecure.basic</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>spnego.login.client.module</param-name>
<param-value>com.sun.security.jgss.krb5.initiate</param-value>
</init-param>
<init-param>
<param-name>spnego.krb5.conf</param-name>
<param-value>conf/krb5.conf</param-value>
</init-param>
<init-param>
<param-name>spnego.login.conf</param-name>
<param-value>conf/login.conf</param-value>
</init-param>
<init-param>
<param-name>spnego.preauth.username</param-name>
<param-value>SERVICE_USER_USERNAME</param-value>
</init-param>
<init-param>
<param-name>spnego.preauth.password</param-name>
<param-value>SERVICE_USER_PASSWORD</param-value>
</init-param>
<init-param>
<param-name>spnego.login.server.module</param-name>
<param-value>com.sun.security.jgss.krb5.accept</param-value>
</init-param>
<init-param>
<param-name>spnego.prompt.ntlm</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>spnego.logger.level</param-name>
<param-value>1</param-value>
</init-param>
</filter>
You
need to replace SERVICE_USER_NAME and SERVICE_USER_PASSWORD with the
ones you use to create your keytab. spnego.allow.basic,
spnego.prompt.ntlm are true to let users who haven't logged into windows
to log in (WARNING: username and password are sent in cleartext/base64
to the linux server!). spnego.allow.unsecure.basic needs to be true if
you don't use https, which you should do.
<filter-mapping>
<filter-name>SpnegoHttpFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
To apply filter to all files.
Create/edit the login.conf:
com.sun.security.jgss.krb5.initiate {
com.sun.security.auth.module.Krb5LoginModule required;
};
com.sun.security.jgss.krb5.accept {
com.sun.security.auth.module.Krb5LoginModule required
doNotPrompt=true
useKeyTab=true
principal="HTTP/myserver.domain.local@DOMAIN.LOCAL"
keyTab="/var/lib/tomcat7/conf/mykeytab.keytab"
storeKey=true
isInitiator=false;
};
Differently
from the apache tutorial, the initate object should only contain the
module, otherwise tomcat will throw a parse error on startup.
Create/edit the krb5.conf:
[libdefaults]
default_realm = DOMAIN.LOCAL
default_keytab_name = FILE:/etc/tomcat7/mykeytab.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]
DOMAIN.LOCAL = {
kdc = 192.168.1.5:88
}
[domain_realm]
domain.local = DOMAIN.LOCAL
.domain.local = DOMAIN.LOCAL
[login]
krb4_convert = true
krb4_get_tickets = false
The
kdc parameter should support the hostname of the KDC/AD server e.g.
kdc.domain.local, but mine had trouble with DNS lookup for it, luckily IP
works fine.
NOTE: Make sure time is within a few minutes of the AD server, consider installing a NTP client to keep in sync.
AUTHORIZATION (roles/groups):
You will need to create/edit the following files (On Ubuntu):
/var/lib/tomcat7/webapps/ROOT/WEB-INF/web.xml
/usr/share/tomcat7/libs/bcprov-jdk15on-147.jar
/usr/share/tomcat7/libs/spnego-pac.jar
Active
Directory adds a blob to their kerberos tokens called PAC (Privilege
Attribute Certificate), which includes a users roles. We can extract
these roles from our ticket so we don't have to do an additional LDAP
request (which is the normal way).
To do this we need a custom build of the spnego library by Ricardo Martín Camarero (rickyepoderi) (see
http://blogs.nologin.es/rickyepoderi/index.php?/archives/73-SPNEGOKerberos-in-JavaEE-PAC.html) which utilizes
JaasLounge and
Bouncy Castle ASN1 to extract the PAC roles.
I've
added support for fetching a users kerberos token when using Basic Auth
as well as adding compressed PAC from another library.
The spnego-pac source code is available from github (
https://github.com/asmund1/spnego-pac), and the final binaries used in this project from
https://github.com/asmund1/spnego-pac/blob/master/jars/spnego-pac.jar and
https://github.com/asmund1/spnego-pac/blob/master/jars/bcprov-jdk15on-147.jar (additional library needed).
On the linux server (as sudo):
Copy spnego-pac.jar and bcprov-jdk15on-147.jar to
/usr/share/tomcat7/libs/ so that tomcat loads it on startup. NOTE:
Remove the original spnego jar if you have it there already.
The
PAC contains only the numerical representation for each role for the
user, you can use this directly in your servlets, but I added some
aliases for my roles. I did this in the webapp web.xml, but it should
work in the global web.xml too (/etc/tomcat7/web.xml):
<context-param>
<param-name>myserver_write_role</param-name>
<param-value>S-1-5-21-123456789-1234567890-1234567890-1234</param-value>
<description>Alias for write access role</description>
</context-param>
To get the value for your roles, check your tomcat log (/var/lib/tomcat7/logs/catalina.out) after login using kerberos, the library prints the SIDs found for a user.
You might have to change the log level since they are printed at FINER level. Do this by appending
net.sourceforge.spnego.SpnegoAuthenticator = FINER
to the bottom of /etc/tomcat/logging.properties file and restarting tomcat.
GET USER AND ROLES (CODE):
The username/SPN of the logged
in user and his/her roles are added to the request object, so to fetch
them in Java servlets / JSP use the following lines of code:
For Java Servlet:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletContext;
public class MyServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
// Fetches username of logged in user
req.getRemoteUser()
// Check if user has write access
ServletContext context = req.getServletContext();
if (!req.isUserInRole(context.getInitParameter("myserver_write_role"))) {
resp.sendError(resp.SC_FORBIDDEN);
return;
}
}
}
For JSP:
<%= request.getRemoteUser() %>
and
<%=
request.isUserInRole(request.getServletContext().getInitParameter("myserver_write_role")) %>
TRUSTED SERVER:
To
get the kerberos token from windows you need to be on the trusted
server list. Chrome and IE use a common list while firefox and other
browsers have their own.
To add you server as trusted
in IE (and Chrome) open Internet Options -> Security -> Local
intranet -> Sites button -> Advanced button
Enter the url for your server and press the Add button.
To do the same for firefox do the following:
1. Open Firefox, and type "about:config" in the Address Bar.
2. In the Search field, type "negotiate".
3. Set the following fields:
network.negotiate-auth.trusted-uris myserver.domain.local
network.negotiate-auth.delegation-uris myserver.domain.local
(
https://bugzilla.mozilla.org/show_bug.cgi?id=520668)
To add your server for IE and Chrome for all windows machines in the intranet, you can use Group Policy:
https://www.serverknowledge.net/group-policy/adding-trusted-sites-internet-explorer-using-group-policy-gpo/
TEST:
On the linux server (as sudo):
Start/Restart tomcat server to load changes in config and load jars:
service tomcat7 restart
Then tail the log output for any errors:
tail -f /var/lib/tomcat7/logs/catalina.out
Then
navigate to your server in IE/Chrome. You should not be prompted for
username or password. If you go to the server from a non-windows logged
on computer (e.g. mobile phone) you should get a popup asking you to
enter username and password. If you enter the incorrect password you
should get a white page, with correct credentials you should see your
content.
Sources:
http://spnego.sourceforge.net/spnego_tomcat.html
http://spnego.sourceforge.net/pre_flight.html
http://spnego.sourceforge.net/reference_docs.html
http://spnego.sourceforge.net/client_keytab.html
http://spnego.sourceforge.net/ExampleSpnegoAuthenticatorValve.javahttp://spnego.sourceforge.net/HelloKeytab.java
https://sourceforge.net/p/spnego/discussion/1003769/thread/98e5ea01/
http://jaaslounge.sourceforge.net/howto/SSO_Tomcat_Howto.pdf
http://www.oracle.com/technetwork/articles/idm/weblogic-sso-kerberos-1619890.html
http://stackoverflow.com/questions/20152000/get-ad-groups-with-kerberos-ticket-in-java
https://tomcat.apache.org/tomcat-7.0-doc/realm-howto.html#JNDIRealm
http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/lab/part6.html
http://kerberos.996246.n3.nabble.com/kinit-Cannot-contact-any-KDC-for-realm-EXAMPLE-COM-while-getting-initial-credentials-td19145.html
http://serverfault.com/questions/166768/kinit-wont-connect-to-a-domain-server-realm-not-local-to-kdc-while-getting-in
http://stackoverflow.com/questions/31877027/kerberos-cannot-find-key-of-appropriate-type-to-decrypt-ap-rep-rc4-with-hmac
https://docs.google.com/document/d/1G7WAaYEKMzj16PTHT_cIYuKXJG6bBcrQ7QQBQ6ihOcQ/edit#heading=h.yh8m8tkjdx9h
http://stackoverflow.com/questions/3568635/android-authenticating-with-kerberos
http://stackoverflow.com/questions/2518256/override-intranet-compatibility-mode-ie8