Building a custom Single Sign-On module for JAVA web apps

The last few weeks I’ve been working on a project that involved Single Sign-On using JAVA web applications. The main purpose of the project was to create a smooth, simple and configurable library that provides SSO to existing JAVA web apps and integrates as easy as possible. In this post I’ll share useful links that provide you all the information you need to embark in a similar journey and some tips that will help you through the way. Along with these, you will find some code that can be used for specific features you have to implement.

To develop and test the library I used the following platforms, environment and technologies. The developing tool was Eclipse [kepler] on a Windows System, and the server selected to host the apps was Tomcat7. Of course you’ll need a JAVA SDK and a STS (in this case was an ADFS 2.0) to provide the security tokens for authentication. So most of the advice within the article will be related to ADFS.

First of all if you’re new to this world, you can start reading about the protocols commonly involved like WS-FED, SAML1 and SAML2. Below you have a list of links that can be used as starting points for each one of these technologies, providing an overall idea of each one and you can use, following the links within them, to get deeper understanding of what is happening under the hood (as you might be implementing authentication requests, logouts, etc):

The next step is to get a library that provides most of the required functionality and pieces you need to build your own solution over it. I found OpenSAML for JAVA one of the best choices for this (http://shibboleth.net/products/opensaml-java.html). As the library description says it does not provide a full implementation of any of the high level entities as Identity Providers, Service Providers, and/or Advanced Clients which is the focus of this article as we will be talking about clients performing SSO.

Integration and SSO features support on the client side can be achieved by implementing a JAVA filter interface (javax.servlet.Filter) that will intercept requests and inspect whether an user is already authenticated within the service provider, or a token is being posted by the STS as a result of an authentication request done before in behalf of the SP.

From this fact you can deduce that you will need custom logic for three main things:

  • Maintain the credentials of an already authenticated user – more likely by implementing a Principal interface (java.security.Principal)
  • Write custom code to validate a posted token in order to authenticate the user and create the Principal instance (this is where you could rely on the OpenSAML library)
  • Initiate new authentication requests automatically using a custom configured STS (that can be done by just a couple of helper classes)

I will not get into details of how to implement the Principal interface or how to handle the Principal instance after a posted token has successfully been validated, but it is trivial that you will store the list of received claims along with the Principal instance and you will handle and keep this object alive as long as required (within a session, cookie or another persistence mechanism allowing some kind of TTL/expiration).

You could have something like shown below:

[sourcecode language=”java”]
public class ClaimsPrincipal implements Principal, Serializable {
private static final long serialVersionUID = 6348413065734100547L;

protected List<Claim> claims = null;
.
.
.
public List<Claim> getClaims() {
return Collections.unmodifiableList(this.claims);
}
}
[/sourcecode]

And for the logic required to validate the posted token and obtain the claims you can create a TokenValidator class (or whatever) that will rely on the functionality provided by the OpenSAML library. This class can have a unique public method called something like validate() which will return the list of claims that you might need to create the Principal instance signaling an authenticated state. This is the most complex task to do and I will not get into details, but you can find useful resources and samples using the OpenSAML library for JAVA on the web.

This the idea of authenticating and managing an user can be summarized with the following pseudo-code:

[sourcecode language=”java”]
TokenValidator validator = new TokenValidator();
try {
List<Claim> claims = validator.validate(postedToken);
PrincipalStore.store(new ClaimsPrincipal(claims));
}
catch (Exception ex) {
[do something with the exception – ex log]
}
[/sourcecode]

And then you can query the Principal Store to check whether an user is already authenticated when receiving new requests or not:

[sourcecode language=”java”]
boolean isAuthenticated = PrincipalStore.retrieve() instanceof ClaimsPrincipal;
[/sourcecode]

But.. What is the user is not already authenticated? We can handle this situation and redirect the request to the STS to start an authentication process. You can do this using WS-FED or SAML2 protocols. The query string required to perform a WS-FED auth request is pretty simple and you can find many examples on the web, and for the SAML2 protocol the easiest way is to implement a HTTP Redirect Binding (http://en.wikipedia.org/wiki/SAML_2.0#HTTP_Redirect_Binding).

As the payload for this can be a little tricky when dealing with ADFS, below is the template that I successfully used against our ADFS. You can use it as well to build a SAML2 auth request to ADFS:

[sourcecode language=”xml”]
<samlp:AuthnRequest
ID="_85e10b08-0f76-4491-be86-07324727c4ed"
Version="2.0"
IssueInstant="2013-08-16T17:12:49.719Z"
Destination="https://[ADFS-URI]/adfs/ls/"
Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
http://[ADFS-URI]/adfs/services/trust
</Issuer>
<Conditions xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
<AudienceRestriction>
<Audience>
[service-provider-urn]
</Audience>
</AudienceRestriction>
</Conditions>
</samlp:AuthnRequest>
[/sourcecode]

As said, with this you can create a valid SAML2 auth request that will be gracefully accepted by ADFS to start a login and post a token back to your service provider. In addition, and as the ADFS can be configured to require signed auth requests, you might need to add signature parameters to your query string (signature algorithm and signature digest). In that case you will need to get a private key from a certificate store in order to perform a rsa-sha1 sign for example.

Here you can see how the code to extract a private key from a keystore could look like:

[sourcecode language=”java”]
public static PrivateKey getPrivateKey(InputStream keyStoreStream, String alias, String password)
throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
FileNotFoundException, IOException, UnrecoverableEntryException {
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(keyStoreStream, password.toCharArray());

KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(alias,
(KeyStore.ProtectionParameter) new KeyStore.PasswordProtection(password.toCharArray()));
PrivateKey pk = pkEntry.getPrivateKey();

return pk;
}
[/sourcecode]

I hope this article has been useful to you, and help you a little in the process of implementing a custom SSO module within your JAVA web app. If you have design/implementation questions you can write me for further details 🙂



2 Comments

Leave a Reply