Single Sign-On Made Easy: SAML With Tomcat and PicketLink
Learn all about SAML single sign-on with PicketLink and Tomcat, including an investigation of how SAML single sign-on works, and overviews of Fediz, Tomcat, and PicketLink.
Join the DZone community and get the full member experience.
Join For FreeHow Does SAML Single Sign-on Work?
In the sequence diagram below you can see the interactions taking place during authentication of a user via SAML SSO. In the sequence diagram below you can see the interactions taking place during authentication of a user via SAML SSO.
The user requests a protected resource from the web application, the application server requires that the user supplies a SAML token that asserts they are authenticated and that they have the necessary rights to the application. So it redirects them to the Identity Provider (IdP) who will give him that SAML token.
The identity provider requires that the user be authenticated too and this takes place perhaps via Kerberos or some other means. Once the Identity Provider is satisfied that the user is authenticated it request metadata about the user to satisfy the assertion requirements of the Service Provider (SP).
It then builds the token and sends it back to the browser. Who then again talks to the application server forwarding it his token. If the assertions stored in the token satisfy the web applications security requirements the user is then authorised to access the application.
Tomcat
The Tomcat project does not include an out of the box solution for Single Sign-On with SAML. I started looking at how to do this and found two possible solutions/libraries. Both seemed quite promising, they both stated “on the box” that they implemented SAML and that they worked with Tomcat. My initial reaction was to try and use the one developed by Apache since I was going to use it with another Apache tool i.e. Tomcat.
Fediz
The Apache library is a sub-project of CXF (an open-source services/integration framework) and the project is called Fediz (an open-source web security framework) and contains tomcat valve which enables this.
The valve is called FederationAuthenticator. However after many trials and tribulations including running Tomcat with a debugger attached, I found that SAML was not supported for SSO because unfortunately the code contains a bug. In the SAMLProcessorImpl there is a method called processRelayState that checks if the requestState parameter is null however a couple of calls up the stack in the SigninHandler processSigninRequest method instead of retrieving the FedizRequest object from the restored Request a new one is created that does not, unfortunately, set the requestState property of the FedizRequest.
Therefore SSO with SAML is currently not possible with Fediz, there were also other issues specific to using it with Active Directory Federation Services (ADFS) so in the end, I abandoned this library and decided to try using the PicketLink library and the ServiceProviderAuthenticator valve. Right now I haven’t had time to investigate all of the problems but at some point in the future I may go back and investigate further trying to fix the issues with the FederationAuthenticator and ask to become a committer to the fediz project but right now that is not possible.
PicketLink
This time, things went a little bit better, as in it worked. Picketlink is pretty much aimed at JBoss users so the documentation for use with Tomcat is quite limited. So what do you need to configure, to have a basic system up and running and working with Active Directory Federation Services using SAML?
- You need to add the PicketLink libraries and dependencies to the Tomcat lib folder.
- You need to add a valve to the application context, this can be done in the server.xml.
- You will need to modify the web.xml file in the WEB-INF folder of your web app.
- You will need to add a config file called picketlink.xml to the WEB-INF folder of your web app.
Step 1.
For the simple configuration, we are putting together here, you will only need 6 jar files. These jar files must be placed in the $CATALINA_HOME/lib folder because they are required by Tomcat itself to enable access to the web app. The required jars are:
- picketlink-common-2.7.2.jar
- picketlink-config-2.7.2.jar
- picketlink-federation-2.7.2.jar
- picketlink-tomcat7-2.7.2.jar
- jboss-logging-3.0.0.GA.jar
- jboss-security-spi-3.0.0.Final.jar
Step 2.
To add the valve to your web app you simply add the following line of XML:
<Valve className="org.picketlink.identity.federation.bindings.tomcat.sp.ServiceProviderAuthenticator" />
to your server config as in the example below within the /myapp context and beware that the tag names are case sensitive:
<?xml version='1.0' encoding='utf-8'?>
<Server port="14772" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JasperListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="conf/ssl-keystore.jks" keyAlias="appserver.mydomain.com" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps">
<Context docBase="myapp.war" path="/myapp">
<Valve className="org.picketlink.identity.federation.bindings.tomcat.sp.ServiceProviderAuthenticator" />
</Context>
</Host>
</Engine>
</Service>
</Server>
Note there is no need for a Realm to be specified in the server.xml and there is also no need to configure JAAS
Step 3.
Things to note about the web.xml are:
- The security roles are the groups/roles the users have in your user database be that LDAP or some other structure.
- The security constraints contain an auth constraint that uses one of these roles.
- There is no login-config section.
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>My Application</display-name> <description>My Web Application</description> <servlet> <servlet-name>WorkerServlet</servlet-name> <servlet-class>com.brett.WorkerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>WorkerServlet</servlet-name> <url-pattern>/internal/worker/*</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>User Space</web-resource-name> <url-pattern>/internal/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> <http-method>PUT</http-method> <http-method>HEAD</http-method> <http-method>TRACE</http-method> <http-method>DELETE</http-method> <http-method>OPTIONS</http-method> </web-resource-collection> <auth-constraint> <role-name>BasicUser</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <security-role> <description>App Users</description> <role-name>BasicUser</role-name> </security-role> </web-app>
Step 4.
Here is where all the magic is. In the PicketLinkSP section, the BindingType refers to the method used for communicating back and forth with the IdP. The IdentityURL is the address of the IdP to be used for requesting the SAML token and the ServiceURL is the address to which the browser should send this token before it will be allowed to receive the resource it required. The trust section contains a list of domains for which the there is a trust relationship.
The handlers section defines which handlers will be involved in interpreting the token during the authentication / authorisation process for the web app. Here we can see that there is a Trust Handler which verifies the rules in the trust section are met, in this case, the domains. There is also a Log Out Handler for un-authenticating the user. An Authentication Handler that checks the validity of the SAML token verifies the Subject (the user) is authenticated and finally that the user has the roles necessary.
The ROLE_KEY option tells the SAML2AuthenticationHandler which of the assertion attributes relate to the roles of the user. If this isn't supplied unfortunately the library doesn't seem to have an intelligent default because it includes everything as a role including the users email address and username.
<PicketLink xmlns="urn:picketlink:identity-federation:config:2.1">
<PicketLinkSP xmlns="urn:picketlink:identity-federation:config:2.1" BindingType="POST">
<IdentityURL>${idp.url::https://dc.mydomain.com/adfs/ls/}</IdentityURL>
<ServiceURL>${myapp.url::https://appserver.mydomain.com:10503/myapp/internal/}</ServiceURL>
<Trust>
<Domains>localhost,mydomain.com</Domains>
</Trust>
</PicketLinkSP>
<Handlers xmlns="urn:picketlink:identity-federation:handler:config:2.1">
<Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2IssuerTrustHandler" />
<Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler" />
<Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler">
<Option Key="ROLE_KEY" Value="http://schemas.microsoft.com/ws/2008/06/identity/claims/role"/>
</Handler>
<Handler class="org.picketlink.identity.federation.web.handlers.saml2.RolesGenerationHandler" />
</Handlers>
</PicketLink>
Pitfalls to Look Out For
The serviceURL in your picketlink.xml should end with a slash and needs to be a URL that requires authentication. Don’t worry what URL the user will be redirected to the URL they originally wanted.
Make absolutely certain that your clocks are synchronized properly. I lost quite a lot of time because my tomcat server was 20 seconds behind my identity server, this resulted in the tokens not yet being valid when they were received but the error reported was that the token had expired. This resulted in a continuous loop of authentication going back and forth between the Identity Provider (IdP) and the Service Provider (SP) / Tomcat
References:
Opinions expressed by DZone contributors are their own.
Comments