My plan is to use Apache Tomcat to deploy Web GUI/ REST/ SOAP or whatever services so I tried to configure Java's buildin Kerberos JAAS authentication in Tomcat.
There is a complicated howto on the web:
https://tomcat.apache.org/tomcat-7.0-doc/windows-auth-howto.html
The howto says that configuration is hard and tricky and does not allow much flexibility.
Kerberos and single sign on (SSO)
For real Kerberos single sign on you need a perfectly configured Tomcat and a so called "kerberised" client, i.e. a modern web broswer.
If the Tomcat deploys a REST service the REST client must be kerberised, too.
In a perfect world all that will be easily available. But we are still far away from that world ;)
Kerberos without SSO
To be honest I do not need SSO for my purpose. It will really be OK if the user or the service consumer has to specify login credentials.
I just do not want to manage these credentials in an extra source. So the authentication must be done against a Kerberos server (i.e. Windows domain controller).
My focus is a central user/password database which does NOT mean SSO.
Unfortunately the howto and other web resoruces do not cover that. I spend some hours on testing and finally got a working solution.
Howto
The following steps show how to setup simple Kerberos authentication for a servlet. I do not explain how to setup and deploy the servel itself. This should be common knowledge.
A real simple Servlet
Let's start with a real simple servlet. It Just prints the system time.
public class HelloWorld extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().append("Served at: ").append(request.getContextPath()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
Please configure your project and web.xml so that you have that simple servlet working. Of course there is no authentication at the moment.
Defining security contraints in web.xml
To force authentication on a servlet you need to define a security constraint in the web.xml
Notes for web.xml
When you deploy the project you will not be able to access the servlet anymore. The browser will show a popup to enter credentials but no matter what you type - access will be denied.
Here comes my jaas config example:
Note: The name of the config (here: DMS) matches the realm-name in web.xml
Edit your Tomcat startup script and specify the path to the jaas config file via:
-Djava.security.auth.login.config=/path/to/jaas.conf
We need 2 other VM arguments:
-Djava.security.krb5.realm=SC.LOCAL
-Djava.security.krb5.kdc=IP/hostname of Kerberos server (domain controller)
Set -Djava.security.krb5.realm to your windows domain (including .local and all uppercase!)
Set -Djava.security.krb5.kdc to your domain controller. If you use a hostname here DNS must work. If you receive problems try to setup an IP here.
Alternative: default realm and kdc can also be set in a krb5.conf file. You can also specify multiple kdcs there. The path to krb5.conf must be passed via a VM option.
Last but not least you must create or modify the file META-INF/context.xml in your project. The context.xml must be deployed with the project.
Notes:
You are now ready to test the setup. Start Tomcat and enter your servlet URL into a browser. Enter a valid login (without domain name) when the popup appears and your servlet will report the system time.
In the Tomcat console you will see a message similar to:
This message reports a successful authentication.
Congratulations if you got it working so far. But what about roles/groups? For now we need to specify users in the web.xml security constraint.
The problem is that the builtin Java Kerberos implementation just authenticates. It does not query groups for the user. So we have no roles.
I will spend more time on that. For the moment I have a very simple solution:
Create a subclass of Krb5LoginModule. I believe it will be perfect to override commit() and do the role logic there but unfortunately there is no way to access the subject there (private member).
So override initialize(...):
This code simply adds a role "Everybody" to the subject. The class RolePrincipal is a simple Java bean which implements javax.security.Principal
Now there are 3 things to change:
No every authenticated user will be able to access the servlet. There is no need to define individual users in web.xml.
TODOs
There are some points to spend further work on:
<servlet-mapping>
<servlet-name>HelloWorld</servlet-name>
<url-pattern>/admin/HelloWorld</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>peter@SC.LOCAL</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>peter@SC.LOCAL</role-name>
</security-role>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>DMS</realm-name>
</login-config>
Notes for web.xml
- the servlet URL is set to admin/HellWorld
- a security constraint is defined for the pattern admin/*. So that constraint applies to the servlet.
- the security constraint requires that the user has the role peter@SC.LOCAL
- the roles has to be defined (security-role)
- the login-config element configures the JAAS realm to use and the way to prompt for the credentials. The JAAS realm is named DMS. With BASIC auth the browser pops up the little htaccess credentials window.
When you deploy the project you will not be able to access the servlet anymore. The browser will show a popup to enter credentials but no matter what you type - access will be denied.
JAAS config
We need a JAAS configuration file and pass that file with the -Djava.security.auth.login.config VM argument to the tomcat server.Here comes my jaas config example:
DMS {
com.sun.security.auth.module.Krb5LoginModule required
debug=true
useTicketCache=false;
};
Note: The name of the config (here: DMS) matches the realm-name in web.xml
Edit your Tomcat startup script and specify the path to the jaas config file via:
-Djava.security.auth.login.config=/path/to/jaas.conf
We need 2 other VM arguments:
-Djava.security.krb5.realm=SC.LOCAL
-Djava.security.krb5.kdc=IP/hostname of Kerberos server (domain controller)
Set -Djava.security.krb5.realm to your windows domain (including .local and all uppercase!)
Set -Djava.security.krb5.kdc to your domain controller. If you use a hostname here DNS must work. If you receive problems try to setup an IP here.
Alternative: default realm and kdc can also be set in a krb5.conf file. You can also specify multiple kdcs there. The path to krb5.conf must be passed via a VM option.
context.xml
<Context>
<Realm className="org.apache.catalina.realm.JAASRealm" appName="DMS"
roleClassNames="javax.security.auth.kerberos.KerberosPrincipal"
/>
</Context>
Notes:
- the realm className has to be set to org.apache.catalina.realm.JAASRealm. That tells tomcat to use JAAS athentication.
- The appName must match the JAAS config name and the realm-name from web.xml
- Important: set roleClassNames to javax.security.auth.kerberos.KerberosPrincipal. Otherwise the subject (here peter@SC.LOCAL) will not be added to the roles and authorisation fails. You will be authenticated but authorisation fails (since you do not have any role).
Test
You are now ready to test the setup. Start Tomcat and enter your servlet URL into a browser. Enter a valid login (without domain name) when the popup appears and your servlet will report the system time.
In the Tomcat console you will see a message similar to:
principal is peter@SC.LOCAL Commit Succeeded
This message reports a successful authentication.
Defining real roles
The problem is that the builtin Java Kerberos implementation just authenticates. It does not query groups for the user. So we have no roles.
I will spend more time on that. For the moment I have a very simple solution:
Create a subclass of Krb5LoginModule. I believe it will be perfect to override commit() and do the role logic there but unfortunately there is no way to access the subject there (private member).
So override initialize(...):
public void initialize(Subject subject,
CallbackHandler callbackHandler,
Map<String, ?> sharedState,
Map<String, ?> options) {
if (subject != null) {
subject.getPrincipals().add(new RolePrincipal("Everyone"));
}
super.initialize(subject, callbackHandler, sharedState, options);
}
This code simply adds a role "Everybody" to the subject. The class RolePrincipal is a simple Java bean which implements javax.security.Principal
public class RolePrincipal implements Principal {
private String name;
public RolePrincipal(String name) {
super();
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
}
Now there are 3 things to change:
- Replace com.sun.security.auth.module.Krb5LoginModule in jaas.conf with the subclassed implementation
- Replace/add RolePrincipal class to roleClassNames attribute in context.xml
- Modify web.xml and use the pseudo role "Everyone" instead of individual users
No every authenticated user will be able to access the servlet. There is no need to define individual users in web.xml.
TODOs
There are some points to spend further work on:
- Use https instead of http. Ensure that credentials are submitted in a secure way
- Implement flexible role configuration. Either configure role assignment in simple way or retrieve groups from AD (but what about nested groups?)
- Test authentication with SOAP and REST services.