Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Stardust/Knowledge Base/Security/Single Sign-on/SSO using JAAS and GSS API with Apache DS and Kerberos"

m
Line 3: Line 3:
 
== Introduction:  ==
 
== Introduction:  ==
  
This tutorial explains what Kerberos protocol is and how it works at large. Then we will see how to implement SSO (Single Sign On) and secure communication using Kerberos. This tutorial will use the Apache DS 1.5.7 and simple Web Service application to demonstrate the SSO. I will use some java security APIs like JAAS and Java GSS (Generic Security Services). It is assumed that you know and understand how these technologies work.  
+
This tutorial explains what Kerberos protocol is and how it works at large. Then we will see how to implement SSO (Single Sign On) and secure communication using Kerberos. This tutorial will use the Apache DS 1.5.7 and simple Web Service application to demonstrate the SSO. We will use java security APIs like JAAS and Java GSS (Generic Security Services). It is assumed that you already know these technologies.
  
 +
In rest of the article, we will see how Kerberos protocol works, how to setup Kerberos service on Apache DS, how to implement SSO and secure communication with detailed server and client side code. I have enclosed java source, properties and config files, discussed here in References section below. 
 +
 
== What is Kerberos and how it works?  ==
 
== What is Kerberos and how it works?  ==
  
Kerberos is a network authentication protocol developed by Internet Engineering Task Force and has been wide adopted by both Java and .Net communities. This protocol doesn’t require that user password be sent over network for authentication.<br>Here is how Kerberos protocol works;<br>Consider there are two systems S1 and S2, which leverage the SSO and secure communication with Kerberos protocol. That means if user U1 is logged into S1, and S1 needs to talk to S2 for fulfilling user request, then user must not be prompted for authentication again but still, S2 must make sure that the user is one who says he is. Means S2 should authenticate the user seamlessly, without user being aware of it. Let’s see how SSO and secure communication works between S1 and S2;  
+
Kerberos is a network authentication protocol developed by Internet Engineering Task Force and has been widely adopted by both Java and .Net communities. This protocol doesn’t require that user password be sent over network for authentication. Rather, it uses concept of tokens and cryptographic keys.<br>Here is how Kerberos protocol works;<br>Consider there are two systems, S1 and S2, which leverage the SSO and secure communication with Kerberos protocol. That means, if user U1 is logs into S1, and S1 needs to talk to S2 for fulfilling user request, then user must not be prompted for authentication again, nor user credentials be passed to S2, but still, S2 must make sure that the user is one who says he is. Means, S2 must authenticate the user seamlessly, without user being aware of it and without user credentials being passed to S2. Let’s see how this happens with Kerberos protocol and how SSO and secure communication works between S1 and S2;  
  
 
#The user U1 logs into S1 with his/her credentials; typically username and password.  
 
#The user U1 logs into S1 with his/her credentials; typically username and password.  
#S1 request the Kerberos ticket form KDC (Key Distribution Center) by sending username only.  
+
#S1 request the Kerberos ticket form KDC (Key Distribution Center) by sending username only. For time being, you may consider KDC means any user repository supporting Kerberos protocol, for example, Microsoft Active Directory or Apache Directory Service.
 
#The KDC issues an encrypted ticket containing session key. The ticket is encrypted by using key derived from user’s password. This ticket is called as TGT (Ticket Granting Ticket). Note that, although user password is not required for requesting TGT, password is required to decrypt it and fetch the session key in it. Without user password TGT is of no use.&nbsp;  
 
#The KDC issues an encrypted ticket containing session key. The ticket is encrypted by using key derived from user’s password. This ticket is called as TGT (Ticket Granting Ticket). Note that, although user password is not required for requesting TGT, password is required to decrypt it and fetch the session key in it. Without user password TGT is of no use.&nbsp;  
#S1 uses user’s password to decrypt the TGT and fetch session key from it and requests one more ticket from KDC for communication with S2. <br>Note that users and systems/services (like U1 and S2 here) must be registered with KDC prior.  
+
#S1 uses user’s password to decrypt the TGT and fetch session key from it and requests one more ticket from KDC for communication with S2. <br>Note that, users and systems/services (like U1 and S2 here) must already be registered with the KDC.  
#Now KDC creates a ticket called as Service Ticket and add the user’s authentication data and a new key called as sub-session key in it. The KDC encrypts the service ticket by key generated from S2’s password. Then, the KDC creates a message, adds encrypted service ticket and sub-session key in it and encrypts the complete message by a key generated from user’s password and sends it back to the S1.<br>Note that, now message contains the sub-session key twice, once directly in the message and then inside encrypted service ticket. The service ticket can be used only for communication between S1 and S2. This complete message can only be decrypted by the user and the enclosed encrypted service ticket can only be decrypted by S2.  
+
#Now the KDC creates a ticket, called as Service Ticket, and adds the user’s authentication data and a new key, called as sub-session key, in it. The KDC encrypts the service ticket by a key generated from S2’s password. Then, the KDC creates a message, adds encrypted service ticket and sub-session key in it, and again encrypts the complete message by a key generated from user’s password, and sends it back to the S1.<br>[Note that, now message contains the sub-session key twice, once directly in the message and inside encrypted service ticket. The service ticket can be used only for communication between S1 and S2. This complete message can only be decrypted by the user and the enclosed encrypted service ticket can only be decrypted by S2.]
 
#The S1 decrypts the message with user’s password and takes out service ticket and sub-session key.  
 
#The S1 decrypts the message with user’s password and takes out service ticket and sub-session key.  
#S1 sends the service ticket (which is encrypted) to S2. The S2 decrypts the service ticket and takes out U1’s authentication data and sub-session key.<br>Now, S2 will trust the user’s authentication data as it could decrypt service ticket with its own password and that means it has come from KDC (which already has authenticated the user) and hence it is authentic. Hence SSO!  
+
#S1 sends the service ticket (which is encrypted) to S2. The S2 decrypts the service ticket and takes out U1’s authentication data and sub-session key.<br>Now, S2 will trust the user’s authentication data as it could decrypt service ticket with its own password, that means it has come from KDC (which already has authenticated the user) and hence it is authentic. Hence SSO!  
#Now, both S1 and S2 have same key (sub-session key) which they will use for secure communication with each other. <br>
+
#Now both, S1 and S2, have the same key (sub-session key) which they will use for secure communication with each other. <br>
  
This is how SSO and secure communication is achieved using Kerberos.  
+
This is how SSO and secure communication is achieved using Kerberos. All these steps mention above are performed by JAAS and GSS API. We will use higher level methods of these security APIs to perform these steps.
  
 
'''Note that KDC functionality is generally implemented by directory servers like MS AD and Apache DS. For this article you may consider KDC means Apache DS. <br>'''
 
'''Note that KDC functionality is generally implemented by directory servers like MS AD and Apache DS. For this article you may consider KDC means Apache DS. <br>'''
  
 
== Setting up KDC with Apache DS  ==
 
== Setting up KDC with Apache DS  ==
 
+
As mentioned above, KDC functionality is generally implemented as part of user repositories like Microsoft Active Directory. For this article I have used Apache DS.<br>
Download Apache DS 1.5.7 and Apache Directory Studio 1.5.3 from Apache site and install it on your local machine. By default, it doesn’t have Kerberos service running. Follow the steps below to activate it. Note that these steps are only valid for DS version 1.5.7.  
+
Download Apache DS 1.5.7 and Apache Directory Studio 1.5.3 from Apache site(http://directory.apache.org/) and install it on your local machine. By default, it doesn’t have Kerberos service running. Follow the steps below to activate it and create user and server account SPN (Service Principle Name). Note that these steps are only valid for DS version 1.5.7.  
  
 
&nbsp; 1.&nbsp; Under your Apache Directory Server install (C:\Program Files\Apache Directory Server), navigate to the folder instances\default\conf. This folder contains the config files for your server instance. server.xml is the Spring context configuration for your server, and contains the Kerberos config settings. Open this file up in a text editor.  
 
&nbsp; 1.&nbsp; Under your Apache Directory Server install (C:\Program Files\Apache Directory Server), navigate to the folder instances\default\conf. This folder contains the config files for your server instance. server.xml is the Spring context configuration for your server, and contains the Kerberos config settings. Open this file up in a text editor.  

Revision as of 09:23, 4 November 2011

Implementing SSO using JAAS and GSS API with Apache DS and Kerberos

Introduction:

This tutorial explains what Kerberos protocol is and how it works at large. Then we will see how to implement SSO (Single Sign On) and secure communication using Kerberos. This tutorial will use the Apache DS 1.5.7 and simple Web Service application to demonstrate the SSO. We will use java security APIs like JAAS and Java GSS (Generic Security Services). It is assumed that you already know these technologies.

In rest of the article, we will see how Kerberos protocol works, how to setup Kerberos service on Apache DS, how to implement SSO and secure communication with detailed server and client side code. I have enclosed java source, properties and config files, discussed here in References section below.

What is Kerberos and how it works?

Kerberos is a network authentication protocol developed by Internet Engineering Task Force and has been widely adopted by both Java and .Net communities. This protocol doesn’t require that user password be sent over network for authentication. Rather, it uses concept of tokens and cryptographic keys.
Here is how Kerberos protocol works;
Consider there are two systems, S1 and S2, which leverage the SSO and secure communication with Kerberos protocol. That means, if user U1 is logs into S1, and S1 needs to talk to S2 for fulfilling user request, then user must not be prompted for authentication again, nor user credentials be passed to S2, but still, S2 must make sure that the user is one who says he is. Means, S2 must authenticate the user seamlessly, without user being aware of it and without user credentials being passed to S2. Let’s see how this happens with Kerberos protocol and how SSO and secure communication works between S1 and S2;

  1. The user U1 logs into S1 with his/her credentials; typically username and password.
  2. S1 request the Kerberos ticket form KDC (Key Distribution Center) by sending username only. For time being, you may consider KDC means any user repository supporting Kerberos protocol, for example, Microsoft Active Directory or Apache Directory Service.
  3. The KDC issues an encrypted ticket containing session key. The ticket is encrypted by using key derived from user’s password. This ticket is called as TGT (Ticket Granting Ticket). Note that, although user password is not required for requesting TGT, password is required to decrypt it and fetch the session key in it. Without user password TGT is of no use. 
  4. S1 uses user’s password to decrypt the TGT and fetch session key from it and requests one more ticket from KDC for communication with S2.
    Note that, users and systems/services (like U1 and S2 here) must already be registered with the KDC.
  5. Now the KDC creates a ticket, called as Service Ticket, and adds the user’s authentication data and a new key, called as sub-session key, in it. The KDC encrypts the service ticket by a key generated from S2’s password. Then, the KDC creates a message, adds encrypted service ticket and sub-session key in it, and again encrypts the complete message by a key generated from user’s password, and sends it back to the S1.
    [Note that, now message contains the sub-session key twice, once directly in the message and inside encrypted service ticket. The service ticket can be used only for communication between S1 and S2. This complete message can only be decrypted by the user and the enclosed encrypted service ticket can only be decrypted by S2.]
  6. The S1 decrypts the message with user’s password and takes out service ticket and sub-session key.
  7. S1 sends the service ticket (which is encrypted) to S2. The S2 decrypts the service ticket and takes out U1’s authentication data and sub-session key.
    Now, S2 will trust the user’s authentication data as it could decrypt service ticket with its own password, that means it has come from KDC (which already has authenticated the user) and hence it is authentic. Hence SSO!
  8. Now both, S1 and S2, have the same key (sub-session key) which they will use for secure communication with each other.

This is how SSO and secure communication is achieved using Kerberos. All these steps mention above are performed by JAAS and GSS API. We will use higher level methods of these security APIs to perform these steps.

Note that KDC functionality is generally implemented by directory servers like MS AD and Apache DS. For this article you may consider KDC means Apache DS.

Setting up KDC with Apache DS

As mentioned above, KDC functionality is generally implemented as part of user repositories like Microsoft Active Directory. For this article I have used Apache DS.
Download Apache DS 1.5.7 and Apache Directory Studio 1.5.3 from Apache site(http://directory.apache.org/) and install it on your local machine. By default, it doesn’t have Kerberos service running. Follow the steps below to activate it and create user and server account SPN (Service Principle Name). Note that these steps are only valid for DS version 1.5.7.

  1.  Under your Apache Directory Server install (C:\Program Files\Apache Directory Server), navigate to the folder instances\default\conf. This folder contains the config files for your server instance. server.xml is the Spring context configuration for your server, and contains the Kerberos config settings. Open this file up in a text editor.

  2.  Set the enabled property to “true” to enable the KDC. By default, it is false.

 
<bean id="kdcConfiguration" 
  class="org.apache.directory.server.kerberos.kdc.KdcConfiguration">
  <!-- Whether to enable the Kerberos protocol. --> 
  <property name="enabled" value="true" /> 
  <!-- The port to run the Kerberos protocol on. --> 
  <property name="ipPort" value="88" /> 
  <!-- <property name="searchBaseDn" value="ou=Users"/> --> 
</bean>

  3.  Uncomment following bean.

<bean class="org.apache.directory.server.core.configuration.MutableInterceptorConfiguration">
  <property name="name" value="keyDerivationService" />  
  <property name="interceptorClassName" 
  value="org.apache.directory.server.core.kerberos.KeyDerivationService" />

  4. You will have to create user and service SPN in DS. You can do this using Apache Directory Studio, but the easiest way is to use LDIF file. You can use this file to define your SPN details. When you place this file at appropriate location, it would be loaded by DS on startup and it creates the specified SPNs. Now create a new .ldif file and add following content in it. It will create a user with name “monkey2” and service with name “webserver2/localhost” under example.com domain.

# Web server2 identity/service principal.
dn: uid=webserver2,ou=users,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: inetOrgPerson
objectclass: krb5Principal
objectclass: krb5KDCEntry
cn: Web Server2
sn: Web Server2
uid: webserver2
userpassword: Password99
krb5PrincipalName: webserver2/localhost@EXAMPLE.COM
# User / client2 principal.
dn: uid=monkey2,ou=users,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: inetOrgPerson
objectclass: krb5Principal
objectclass: krb5KDCEntry
cn: Monkey2
sn: Monkey2
uid: monkey2
userpassword: Password100
krb5PrincipalName:monkey2@EXAMPLE.COM
# Ticket Granting Service2.
dn: uid=krbtgt2,ou=users,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: inetOrgPerson
objectclass: krb5Principal
objectclass: krb5KDCEntry
cn: KDC Service2
sn: KDC Service2
uid: krbtgt2
userpassword: randomKey
krb5PrincipalName: krbtgt2/EXAMPLE.COM@EXAMPLE.COM

  5.  Store this file under ldifDirectory directory. For location of your ldifDirectory search server.xml.

  6.  And in the end restart the DS server for changes to apply.

  7. To verify that your ldif file is loaded properly. Start the Apache Directory Studio and create new connection to your DS server with following details;

Hostname: localhost
Port: 10389
Encryption Method: No encryption
Authentication Method: Simple Authentication
Bind DN or user: uid=admin,ou=system
Bind password: secret 

  8.  If ldif file was loaded successfully, it will have setup the client principal, the server principal, and the krbtgt service. That is, you now have a fully functioning KDC. Now you can try running your Kerberos example against the KDC.

 

Writing the Server Part

  Here I have implemented the server part as simple JAX-RS web service using CXF. You may take the core idea form here and implement the server part as per your requirement like Servlet Filter or SOAP handler or as any component that fits your need.

Server Class:

This class exposes the EchoService class as JAX-RS service at URL http://localhost:9000/

package com.gyan.rs;
 
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
 
public class Server {
 
	protected Server() throws Exception {
        JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
        sf.setResourceClasses(EchoService.class);
        sf.setResourceProvider(EchoService.class, 
            new SingletonResourceProvider(new EchoService()));
        sf.setAddress("http://localhost:9000/");
 
        sf.create();
    }
 
    public static void main(String args[]) throws Exception {
        new Server();
        System.out.println("Server ready...");
        Thread.sleep(5 * 60 * 1000);
        System.out.println("Server exiting");
        System.exit(0);
    }
}
 

EchoService Class:

This class is part of our example business service layer. It will just extract the authentication details, Kerberos token in this case, from incoming request and verify it with already initialized server context using utility class called GSSServer. If verification fails, it throws the security exception. Note the usage of Base 64 encoding and decoding to avoid sending raw Kerberos tokens and URL sage messages across client and server.

package com.gyan.rs;
 
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
 
import org.apache.commons.codec.binary.Base64;
import org.gyan.server.GSSServer;
import org.ietf.jgss.GSSException;
 
@Path("/echoservice/")
public class EchoService {
 
	private GSSServer gssServer;	
	public EchoService(){
		gssServer = new GSSServer();
        gssServer.startServer();
	}
	@GET
	@Path("/{msg}/")
	public String saySomthing(@PathParam("msg") String clientMsg, @DefaultValue("NA") 
			@HeaderParam("Authorization") String kerberosToken) throws GSSException {
 
		System.out.println("auth:"+ kerberosToken);
		System.out.println("Encrypted message as is: "+ clientMsg);
		byte[] convert = Base64.decodeBase64(kerberosToken);
		try {
			gssServer.authenticate(convert);
			byte[] unWrapMessage = gssServer.unWrapMessage(Base64.decodeBase64(clientMsg));
			clientMsg = new String(unWrapMessage);
		} catch (GSSException e) {
			e.printStackTrace();
		}
		System.out.println("Decrypted Msg: "+ clientMsg);
		return  Base64.encodeBase64URLSafeString(gssServer.wrapMessage(
				("You said '" + clientMsg + "'").getBytes()));
	}	
}

GSS Server Properties:

 KDC and other properties are store as part of gssserver.properties file as shown below. It is used to store location of KDC server, realm/domain, and JAAS config details.

#This file contains the KDC server details and Server service SPN details
Server.SPN = webserver2/localhost@EXAMPLE.COM
Server.Password = Password99
KDC.Realm = EXAMPLE.COM
KDC.Address = localhost
JAAS.Config.Path = C:/Users/ganesh.lawande/workspace/SSOProject/resources/jaas.conf
JAAS.Config.Name = GSSServer

JAAS Config File:

The JAAS conf file for client and server side details looks like following;

GSSClient{
  com.sun.security.auth.module.Krb5LoginModule required
  useTicketCache=false;
};
GSSServer{
  com.sun.security.auth.module.Krb5LoginModule required
  storeKey=true;
};


GSSServer Class:

This class is initialized during service creation. It abstracts out the authentication and communication with KDC server using JAAS and GSS API. This class uses the server properties mentioned above to initialize the security context. Later on when, client request comes in, client’s Kerberos token will be validated against this security context. Note the usage of GSSContext class which is part of GSS API to authenticate the client’s Kerberos token and encrypt (wrap method) and decrypt (unwrap method) the messages between client and server. Initialized GSSContext will have the required keys for given client.


package org.gyan.server;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Properties;
 
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
 
import org.gyan.BeanCallbackHandler;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.MessageProp;
import org.ietf.jgss.Oid;
 
public class GSSServer implements java.security.PrivilegedAction {
 
	// Handles callback from the JAAS framework.
	BeanCallbackHandler beanCallbackHandler = null;
 
	// The main object that handles all JAAS login.
	LoginContext serverLC = null;
 
	// The context for secure communication with client.
	GSSContext serverGSSContext = null;
 
	// Socket and streams used for communication.
	ServerSocket serverSocket = null;
	DataInputStream inStream = null;
	DataOutputStream outStream = null;
 
	// Name and port of server.
	String serverName = null;
	int serverPort;
 
	// Configuration file and the name of the client configuration.
	String confFile = null;
	String confName = null;
 
	private String clientName;
 
	public static void main(String[] args) throws IOException, GSSException {
		// Starting the server.
		new GSSServer().startServer();
 
	}// main
 
	// GSSServer constructor
	public GSSServer() {
		Properties env = new Properties();
		try {
			env.load(this.getClass().getClassLoader()
					.getResourceAsStream("gssserver.properties"));
		} catch (IOException e) {
			e.printStackTrace();
		}
 
		this.serverName = (String) env.getProperty("Server.SPN");
		this.confName = (String) env.getProperty("JAAS.Config.Name");
 
		beanCallbackHandler = new BeanCallbackHandler(this.serverName,
				(String) env.getProperty("Server.Password"));
		setSystemProperties((String) env.getProperty("KDC.Realm"),
				(String) env.getProperty("KDC.Address"),
				(String) env.getProperty("JAAS.Config.Path"));
	}// GSSServer
 
	private void setSystemProperties(String kerberosRealm, String kdcAddress,
			String confFile) {
		System.setProperty("java.security.krb5.realm", kerberosRealm);
		System.setProperty("java.security.krb5.kdc", kdcAddress);
		System.setProperty("java.security.auth.login.config", confFile);
		System.setProperty("sun.security.krb5.debug", "true");
	}
 
	public boolean startServer() {
		try {
			serverLC = new LoginContext(confName, beanCallbackHandler);
			serverLC.login();
			Subject.doAs(serverLC.getSubject(), this);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			System.out
					.println(">>> GSSServer...Secure Context not established..");
			return false;
		}// catch
 
	}// start
 
	public Object run() {
		try {
			GSSManager manager = GSSManager.getInstance();
			Oid kerberos = new Oid("1.2.840.113554.1.2.2");
			GSSName serverGSSName = manager.createName(serverName,
					GSSName.NT_USER_NAME);
			GSSCredential serverGSSCreds = manager.createCredential(
					serverGSSName, GSSCredential.INDEFINITE_LIFETIME, kerberos,
					// The server accepts secure context request.
					GSSCredential.ACCEPT_ONLY);
			serverGSSContext = manager.createContext(serverGSSCreds);
		}// try
		catch (java.lang.Exception e) {
			e.printStackTrace();
		}
		return null;
	}// run
 
	public void authenticate(byte[] kerberosToken) throws GSSException {
		if (!serverGSSContext.isEstablished()) {
			serverGSSContext.acceptSecContext(kerberosToken, 0,
					kerberosToken.length);
			clientName = serverGSSContext.getTargName().toString();
		}
	}
 
	public byte[] wrapMessage(byte[] msg) throws GSSException {
		MessageProp msgProp = new MessageProp(0, false);
		return serverGSSContext.wrap(msg, 0, msg.length, msgProp);
	}
 
	public byte[] unWrapMessage(byte[] msg) throws GSSException {
		MessageProp msgProp = new MessageProp(0, false);
		return serverGSSContext.unwrap(msg, 0, msg.length, msgProp);
	}
 
	public void closeServerContext() throws GSSException {
		// Disposing and closing client and server sockets.
		serverGSSContext.dispose();
	}
 
	public String getClientName() {
		return clientName;
	}
}// End of GSSServer


How Client Side Looks Like

Like server side, at client site I have used utility class, GSSClient, which abstracts out the communication with KDC.


Client Class:

The client class uses the GSSClient class to pass username and password to authenticate the user and get the Kerberos token (service ticket for given server). Note that token is in byte array format. So we used Base 64 encoding to convert it into string and then we simply add it as part of authentication header. That’s it there in client. So, actual stuff happens in GSSClient which is discussed next;


package com.gyan.rs;
 
import java.io.IOException;
 
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.gyan.GSSClient;
import org.ietf.jgss.GSSException;
 
public class Client1 {
 
	private static GSSClient gssClient;
 
	/**
	 * @param args
	 * @throws IOException
	 * @throws HttpException
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws HttpException, IOException, InterruptedException {
 
		String theClientCred = authenticateAndGetKerberosToken();
 
 
			String serviceAddress = "http://localhost:9000/echoservice/";
			String message = "Hi man, how are u?";
			System.out.println(message);
			try {
				byte[] wrapedMsg = gssClient.wrapMessage(message.getBytes());
				serviceAddress += Base64.encodeBase64URLSafeString(wrapedMsg);
			} catch (GSSException e) {
				e.printStackTrace();
			}
			GetMethod get = new GetMethod(serviceAddress);
 
			get.setRequestHeader("Authorization", theClientCred);
			HttpClient httpclient = new HttpClient();
 
			try {
				int result = httpclient.executeMethod(get);
				System.out.println("Response status code: " + result);
				System.out.println("Response body: ");
				System.out.println("Return message from server as is: "
						+ get.getResponseBodyAsString());
				System.out.println("After decryptionm: "
						+ new String(gssClient.unWrapMessage(Base64
								.decodeBase64(get.getResponseBodyAsString()))));
			} catch (GSSException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				// Release current connection to the connection pool once you are done
				get.releaseConnection();
			}
//			Thread.sleep(10000);
		}
 
	private static String authenticateAndGetKerberosToken() {
		gssClient = new GSSClient("monkey", "Password99");
		String serviceTicket = Base64.encodeBase64URLSafeString(gssClient.login());
		return serviceTicket;
	}
}//End of Class

 

Client Side Properties:

The client side properties are stored as part of gssclient.properties file. It contains the server SPN, KDC Server Address, realm/domain, and JAAS properties. 

#This file contains the KDC server details and Remote peer details
Server.SPN = webserver2/localhost@EXAMPLE.COM
Server.Address = localhost
KDC.Realm = EXAMPLE.COM
KDC.Address = localhost
JAAS.Config.Path = C:/Users/ganesh.lawande/workspace/SSOProject/resources/jaas.conf
JAAS.Config.Name = GSSClient


GSSClient Class:

This class uses the JAAS and GSS API to authenticate and create security context against given KDC. Once client initializes the security context, it can be used for SSO and secure communication with given server.
Here KDC details are loaded from gssclient.properties file. Note that how client side uses initSecContext method of GSSContext to establish connection with the server.


package org.gyan;
 
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Properties;
 
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
 
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.MessageProp;
import org.ietf.jgss.Oid;
 
public class GSSClient implements java.security.PrivilegedAction<byte[]> {
 
	// Handles callback from the JAAS framework.
	private BeanCallbackHandler beanCallbackHandler = null;
 
	// The main object that handles all JAAS login.
	LoginContext peerLC = null;
 
	// Socket and streams used for communication.
	Socket socket = null;
	DataInputStream inStream;
	DataOutputStream outStream;
 
	// This and remote clients.
	String clientName = null;
	String serverName = null;
 
	// Address and port of remote server.
	String serverAddress = null;
	int serverPort;
 
	// The name of the client configuration.
	String confName = null;
 
	private GSSContext clientGSSContext = null;
 
	/**
	 * @param clientName, Client SPN 
	 * @param password, client password
	 */
	public GSSClient(String clientName, String password){
		Properties env = new Properties();
		try {
			env.load(this.getClass().getClassLoader().getResourceAsStream("gssclient.properties"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.clientName = clientName;
		this.serverName = (String)env.getProperty("Server.SPN");
		this.serverAddress = (String)env.getProperty("Server.Address");
		this.confName = (String)env.getProperty("JAAS.Config.Name");
 
		beanCallbackHandler = new BeanCallbackHandler(clientName, password);
		setSystemProperties((String)env.getProperty("KDC.Realm"),
				(String)env.getProperty("KDC.Address"),
				(String)env.getProperty("JAAS.Config.Path"));
	}
	/**
	 * The GSSClient constructor only sets all the required parameters.
	 * 
	 * @param clientName, Client SPN 
	 * @param password, client password
	 * @param serverName, Remote server/service SPN
	 * @param serverAddress, Remote Server address IP or Hostname 
	 * @param kerberosRealm, KDC realm or domain name
	 * @param kdcAddress, KDC Server address IP or Hostname 
	 * @param confFile, fll path of JAAS COnfig file
	 * @param confName, JAAS config name
	 */
	public GSSClient(String clientName, String password, String serverName,
			String serverAddress, String kerberosRealm,
			String kdcAddress, String confFile, String confName){
		// The beanCallbackHandler will require the name and password of the
		// client.
		beanCallbackHandler = new BeanCallbackHandler(clientName, password);
		this.clientName = clientName;
		this.serverName = serverName;
		this.serverAddress = serverAddress;
		this.confName = confName;
		setSystemProperties(kerberosRealm, kdcAddress, confFile);
	}// KerberoseLoginBean
 
	private void setSystemProperties(String kerberosRealm, String kdcAddress,
			String confFile) {
		System.setProperty("java.security.krb5.realm", kerberosRealm);
		System.setProperty("java.security.krb5.kdc", kdcAddress);
		System.setProperty("java.security.auth.login.config", confFile);
		System.setProperty("sun.security.krb5.debug", "true");
	}
 
	public byte[] login() {
		try {
			peerLC = new LoginContext(confName, beanCallbackHandler);
			peerLC.login();
			return Subject.doAs(peerLC.getSubject(), this);
		} catch (Exception e) {
			System.out
					.println(">>>> GSSClient....Secure Context not established..");
			e.printStackTrace();
			return null;
		}// catch
 
	}// establishSecureContextWithServer
 
	// This is the only method in PrivilegedAction interface.
	// It receives control only in case of successful authentication of the
	// client.
	public byte[] run() {
 
		try {
			GSSManager manager = GSSManager.getInstance();
			Oid kerberos = new Oid("1.2.840.113554.1.2.2");
			GSSName clientPeerName = manager.createName(
			// Name of the client for which we want to create this GSSName
			// object.
					clientName,
					// Type of GSSName. Our client is a Windows user,
					// which we can specifiy using GSSName.NT_USER_NAME
					// property.
					GSSName.NT_USER_NAME);
			// GSSName remotePeerName2 = manager.createName("FakeSERver",
			// GSSName.NT_USER_NAME);
			GSSName remotePeerName = manager.createName(serverName,
					GSSName.NT_USER_NAME);
			System.out.println(">>> GSSClient... Getting client credentials");
 
			GSSCredential peerCredentials = manager.createCredential(
			// The GSSName object of the client.
					clientPeerName,
					// Time for which credentials whill be valid.
					10 * 600,
					// Kerberos mecahnism identifier.
					kerberos,
					// The client only intiates the secure context request.
					GSSCredential.INITIATE_ONLY);
 
			System.out
					.println(">>> GSSClient...GSSManager creating security context");
			clientGSSContext= manager.createContext(remotePeerName, kerberos,
					peerCredentials, GSSContext.DEFAULT_LIFETIME);
 
			// peerContext.requestCredDeleg(true);
			clientGSSContext.requestConf(true);
			clientGSSContext.requestMutualAuth(false);// I don't want to authenticate the 
														// Server.
			byte[] byteToken = new byte[0];
 
			byteToken = clientGSSContext.initSecContext(byteToken, 0,
						byteToken.length);
			return byteToken;
		}// try
		catch (org.ietf.jgss.GSSException ge) {
			ge.printStackTrace();
			System.out.println(">>> GSSClient... GSS Exception "
					+ ge.getMessage());
		}
 
		catch (java.lang.Exception e) {
			e.printStackTrace();
			System.out.println(">>> GSSClient...  Exception " + e.getMessage());
		}// catch
		return null;
	}// run
 
	// It returns the established login context to client.
	public LoginContext getLoginContext() {
		return peerLC;
	}// getloginContext
 
	public byte[] wrapMessage(byte[] msg) throws GSSException {
		MessageProp msgProp = new MessageProp(0, false);
		return clientGSSContext.wrap(msg, 0, msg.length, msgProp);
	}
 
	public byte[] unWrapMessage(byte[] msg) throws GSSException {
		MessageProp msgProp = new MessageProp(0, false);
		return clientGSSContext.unwrap(msg, 0, msg.length, msgProp);
	}
 
}// End of GSSClient

Summary

Now you can run the server side with Server class’s main method. Note that before your run Server, Apache DS must be running and required user and service/server SPN must be created as mentioned above.

If server is started successfully, you can run the client side, Client1 class here, and see that initSecContext method on GSSContext goes fine. This means user authentication details have been securely communicated to the server and it has accepted the credentials. Meaning SSO has taken place. That is user has been authenticated by server without asking end user its credentials and without passing the user credentials across network. Here security context is established means, user has been authenticated and appropriate cryptographic keys have been made between client and server. These keys are available in GSSContext and you can use this context’s high level methods to encrypt and decrypt the messages for secure communication between client and server.

Now, once authentication has taken place and security context is created, client can securely communicate with server by using encrypting the messages and sending then over to server using normal URL or Form parameters over HTTP. This is end-to-end security as opposed to point to point security offered by HTTPS. Here we attach the encrypted message to URL, which server side will extract like normal URL parameter and decrypt is using already shared keys, using GSSContext at server side. Here we have used Base 64 encoding to convert encrypted message into URL safe format. Also you will see at server side this URL parameter is first decoded using Base 64 and then decrypted using GSSContext.


References

1. The java source code, properties files, ldif and config files discussed in this article are enclosed here; 

SSO_Example_Source.zip (http://wiki.eclipse.org/images/9/91/SSO_Example_Source.zip)

2. http://www.ibm.com/developerworks/java/library/j-gss-sso/, by Faheem Khan

3. http://thejavamonkey.blogspot.com/2008/07/using-apache-directory-server-as-kdc.html, for setting Apache DS as KDC

4. http://directory.apache.org/, the Apache Directory™ Project

 

 

Back to the top