Dienstag, 13. September 2016

Kerberos Authentication with Tomcat 8.0 and pure Java

I am working on service/web based search platform for indexing and querying file servers. One of the key challenges is authentication against the same resource as the fileserver itself does. In most cases this will be an Active Directory or something similar.

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)


The Tomcat howto focuses on single sign on. This means that your Windows login credentials (or precisely a ticket) is passed from the browser to the Tomcat webserver and is used for authentication. There is no need to prompt for credentials.

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


 <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


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.


 <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


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(...):


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:


  1. Use https instead of http. Ensure that credentials are submitted in a secure way
  2. Implement flexible role configuration. Either configure role assignment in simple way or retrieve groups from AD (but what about nested groups?)
  3. Test authentication with SOAP and REST services.


Keine Kommentare:

Kommentar veröffentlichen