Saturday, March 29, 2008

JAAS + Kerberos Auth

After digging around for quite a while (the docs on this TOTALLY suck), if you need to do Kerberos authentication from your app with JAAS + 1.5:

package your.app;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

public class KerberosAuth {
 public static void main(String[] args) {
   String userName = "myuser@MYREALM.COM";
   char[] password = "mypassword".toCharArray();

   System.setProperty("java.security.auth.login.config", KerberosAuth.class.getResource("/kerberos.conf").toExternalForm());
   System.setProperty("java.security.krb5.realm", "MYREALM.COM");
   System.setProperty("java.security.krb5.kdc", "mykdc.MYREALM.COM");
   try {
     LoginContext lc = new LoginContext("primaryLoginContext", new UserNamePasswordCallbackHandler(userName, password));
     lc.login();
     System.out.println("KerberosAuth.main: " + lc.getSubject());
   }
   catch (LoginException le) {
     le.printStackTrace();
   }
 }

 public static class UserNamePasswordCallbackHandler implements CallbackHandler {
   private String _userName;
   private char[] _password;

   public UserNamePasswordCallbackHandler(String userName, char[] password) {
     _userName = userName;
     _password = password;
   }

   public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
     for (Callback callback : callbacks) {
       if (callback instanceof NameCallback && _userName != null) {
         ((NameCallback) callback).setName(_userName);
       }
       else if (callback instanceof PasswordCallback && _password != null) {
         ((PasswordCallback) callback).setPassword(_password);
       }
     }
   }
 }
}
put kerberos.conf (in this example) inside your Sources folder with the contents:
primaryLoginContext {
 com.sun.security.auth.module.Krb5LoginModule required client=true useTicketCache=false;
};

"java.security.auth.login.config" can map to a File or a URL. Annoyingly it appears that it cannot map to the actual contents of the file -- it has to be loaded from a URL, which seems completely stupid to me (as does just about all of the JAAS/GSSAPI api's, which were clearly written by "cryptography engineers" and not "normal humans").

I have not tried this against Active Directory, yet, just Open Directory ... I'll be trying Active Directory soon and post with info. One thing that's actually pretty damn slick is that if you need this in a Java client application (i.e. non-web-app) you can set "useTicketCache=true" and it will actually use your Kerberos info from the OS ticket cache, which means it actually does proper single sign-on. You can also combine this (which is what we plan to do) with SPNEGO using mod_krb5 on Apache. So you can have mod_krb5 do SPNEGO auth (and just read the user info from the remote user header), and use this as a fall-back if the user is not using a SPNEGO-compatible browser.

2 comments:

10verwrite said...

Thank you very much. Exactly what I was looking for. Worked instantly. Please take care to write your realm and kdc in upper case.

Rick Gan said...

Your code is working... thanks Mike... and i yet to use it to test on my Active Directory.