30 January 2015

Single Logout with Fediz - WS-Federation

WS-Federation is primarily used to achieve Single Sing On (SSO). This raises the challenge how to securely logout from multiple applications once the user is done with his work. To navigate to each application previously used to hit the logout button would be quite inconvenient. Fortunately the WS-Federation standard does not only define how to do single sign on, but also how to do single logout.

In this blog I'll explain how to setup a demonstrator to show single sing-on as well as single sing-off. Since single sing-off is implemented in CXF Fediz version 1.2, I'm going to use a snapshot build since 1.2 is not yet released.
First of all we need to download Tomcat 7 since we will deploy our IDP/STS as well as our two demo applications to a tomcat container each. I renamed the tomcat folder of my extracted tomcat zip to:
  • Fediz-IDP
  • Fediz-RP1
  • Fediz-RP2
Next I opened a terminal within the cxf-fediz source code which I downloaded from github and run maven to build fediz:
mvn clean install

Setup IDP

After my build was successfull I copied the fediz-idp-sts.war file from cxf-fediz/services/sts/target/ into my Fediz-IDP/webapps/ deployment folder. I also did the same with the fediz-idp.war file from cxf-fediz/services/idp/target/.
Since the default https fediz port for the IDP and STS is 9443 and also to avoid port collisions with my two other tomcat instances, I need to update the port configuration in my tomcat Fediz-IDP/conf/server.xml. Here I update all ports starting with '8' to start with a '9'.
<Connector port="9443" protocol="org.apache.coyote.http11.Http11Protocol"
     maxHttpHeaderSize="65536"
     maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
     keystoreFile="idp-ssl-key.jks"
     keystorePass="tompass"
     truststoreFile="idp-ssl-trust.jks"
     truststorePass="ispass"
     truststoreType="JKS" 
     clientAuth="want"
     sslProtocol="TLS" />
To enable SSL for my RP-IDP tomcat I need to provide a keystore as well as a truststore. For demo purposes I will simply copy the java key stores from my fediz build cxf-fediz/services/idp/target/classes/ here I find the file idp-ssl-key.jks as well as idp-ssl-trust.jks which I'll copy to my Fediz-IDP root folder.
Before you can start Fediz-IDP you also need to get the expected JDBC driver which is by default HyperSQL JDBC driver. You need to download the zip file and then extract all jar files from /hsqldb-2.3.2/hsqldb/lib/ to Fediz-IDP/lib/.
Now you can start the Fediz-IDP tomcat server via Fediz-IDP/bin/startup.sh.
To avoid OutOfMemory erros you should add the following settings to your CATALINA_OPTS system environment variable: -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled -XX:MaxPermSize=128M

By default the Fediz IDP has only basic authentication activated for user login. This is done to make it easier to run some system tests. However for single logout HTTP Basic authentication is not recommended, because the browser will cache your user credentials and will automatically sent your credentials to the IDP. So you would have to close all your current browser windows to actually see the login popup again after logout. If you also enable form based authentication in your webapps/fediz-idp/WEB-INF/security-config.xml you will actually see a login form again after your logout action. Here is the sample configuration how to enable form based authentication:
<security:http use-expressions="true">
 <security:custom-filter after="CHANNEL_FILTER" ref="stsPortFilter" />
 <security:custom-filter after="SERVLET_API_SUPPORT_FILTER" ref="entitlementsEnricher" />
 <security:intercept-url pattern="/FederationMetadata/2007-06/FederationMetadata.xml" access="isAnonymous() or isAuthenticated()" />

 <!-- MUST be http-basic thus systests run fine -->
 <security:form-login />
 <security:http-basic />
 <security:logout delete-cookies="FEDIZ_HOME_REALM,JSESSIONID" invalidate-session="true" />
</security:http>
You can also disable http basic authentication if you want to. But you can also just leave it enabled. In that case you can use both authentication styles. You will see an HTML authentication form if you are requested to login, but you could also provide HTTP-Basic authentication header to login.

After you updated the IDP configuration you need to restart the IDP tomcat server to apply your changes.

Setup 1. Demo App

First of all we must provide the Fediz plugin dependencies to our RP tomcat container. For this purpose we need to create a fediz subfolder in Fediz-RP1/lib/. Next we extract the content of the tomcat plugin dependencies zip file (cxf-fediz\plugins\tomcat\target\fediz-tomcat-1.2.0-SNAPSHOT-zip-with-dependencies.zip) to the fediz subfolder.
To make sure that tomcat loads these additional dependencies we must also update the calatina.properties in Fediz-RP1/conf.
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,${catalina.home}/lib/fediz/*.jar
For Fediz-RP1 we will keep all port settings as they are. To keep things simple with the SSL connection we will reuse the idp-ssl-key.jks keystore from the Fediz-IDP and copy this keystore also to Fediz-RP1 root folder. The server.xml file needs to have the following SSL connector to be configured for Fediz-RP1:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
     maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
     keystoreFile="idp-ssl-key.jks"
     keystorePass="tompass"
     clientAuth="false"
     sslProtocol="TLS" />
Before we start the demo app container, we need to copy the demo app to the webapps folders, which can be found at cxf-fediz/examples/simpleWebapp/target/fedizhelloworld.war.
Finally we must provide a correct fediz configuration file to the config folder of the demo app container. For this purpose we can copy the demo config file from cxf-fediz/examples/simpleWebapp/src/main/config/fediz_config.xml to Fediz-RP1/conf/.
To make sure that the SAML tokens issued by the STS can be validated at the RP we must also install the correct STS truststore. This we can do by copying cxf-fediz/services/sts/target/classes/ststrust.jks to Fediz-RP1 root folder.
Now everything should be in place so that we can start Fediz-RP1.

We should see no exceptions in the logfiles and we should see the metadata document from the RP at the following URL: https://localhost:8443/fedizhelloworld/FederationMetadata/2007-06/FederationMetadata.xml

Setup 2. Demo App

The second demo app will be quite similar to the first. Therefore we can simply copy the Fediz-RP1 folder and rename it to Fediz-RP2. To avoid port collisions, we also need to update some server ports.
Therefore we will update all ports beginning with a leading '8' and replace it with a leading '7' in the Fediz-RP2/conf/server.xml file.

Since we are going to start both tomcat container on the same machine (localhost), we must also change the context path of the second demo app. Otherwise both apps would use the same cookies. Thus we need to rename the fedizhelloworld.war file within the Fediz-RP2/webapps/ folder to fedizhelloworld2.war.

To also make this application known at the IDP, you need to register this application via the IDP REST Interface. You can use SoapUI for example or simply curl from your command line.

POST https://localhost:9443/fediz-idp/services/rs/applications
<ns2:application xmlns:ns2="http://org.apache.cxf.fediz/">
     <realm>urn:org:apache:cxf:fediz:fedizhelloworld2</realm>
     <role>ApplicationServiceType</role>
     <serviceDisplayName>Fedizhelloworld</serviceDisplayName>
     <serviceDescription>Web Application to illustrate WS-Federation</serviceDescription>
     <protocol>http://docs.oasis-open.org/wsfed/federation/200706</protocol>
     <tokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</tokenType>
     <lifeTime>3600</lifeTime>
</ns2:application>
Next you need to add all claims required for the helloworld application. Since the claim types are already known by the default fedizhelloworld application you only need to add a link between application and claims:

POST https://localhost:9443/fediz-idp/services/rs/applications/urn%3Aorg%3Aapache%3Acxf%3Afediz%3Afedizhelloworld2/claims 
<ns2:requestClaim xmlns:ns2="http://org.apache.cxf.fediz/">
      <claimType>http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role</claimType>
      <optional>false</optional>
</ns2:requestClaim>
<ns2:requestClaim xmlns:ns2="http://org.apache.cxf.fediz/">
      <claimType>http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname</claimType>
      <optional>true</optional>
</ns2:requestClaim>
<ns2:requestClaim xmlns:ns2="http://org.apache.cxf.fediz/">
      <claimType>http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname</claimType>
      <optional>true</optional>
</ns2:requestClaim>
<ns2:requestClaim xmlns:ns2="http://org.apache.cxf.fediz/">
      <claimType>http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress</claimType>
      <optional>true</optional>
</ns2:requestClaim>
Next you need to register this application to a given IDP realm.
POST https://localhost:9443/fediz-idp/services/rs/idps/urn%3Aorg%3Aapache%3Acxf%3Afediz%3Aidp%3Arealm-A/applications
<ns2:application xmlns:ns2="http://org.apache.cxf.fediz/">
      <realm>urn:org:apache:cxf:fediz:fedizhelloworld2</realm>
</ns2:application>
You can check if your application was registered correctly via GET https://localhost:9443/fediz-idp/services/rs/idps.
 Now the IDP will be able to provide SAML token for the second demo application.

Test Single Sign-On

To test if single sign-on is working as expected you can open the following URL in your browser: https://localhost:8443/fedizhelloworld/secure/fedservlet. You should get redirected to the IDP and need to choose Realm-A as your home realm. Next you need to enter your credentials bob:bob.
You should be redirected back to the fedservlet URL and should see your username, assigned roles as well as  other claims.

If you now enter https://localhost:7443/fedizhelloworld/secure/fedservlet in your browser you should get redirected to the IDP and then without the need to enter your credentials again the IDP should redirect you back to the demo application.

Congratulation. Single Sing-on is working!

Test Single Sign-Off

Goal of this blog post was not to achieve single sign-on but rather single sign-off. For this you have two options to trigger single logout:
  1. You can invoke a logout request starting at the demo application:
    https://localhost:8443/fedizhelloworld/secure/logout
  2. You can invoke a logout request directly at the IDP:
    https://localhost:9443/fediz-idp/federation?wa=wsignout1.0
After you triggered the logout process you will be redirected to a page listing all application which the IDP had previously issued security tokens for. You will also be asked if you really want to logout from all these applications. After you confirmed the logout request, you should see a confirmation page. This page contains the same list of applications as before but this time with a green check maker at the end of each line.

This image is the key to preform the actual logout for all the remote applications. The image resource URL will point to the logout URL of these applications, and by resolving the image resource in your browser you will also invoke the logout URL off all these applications.

If you invoke now any of the two applications you should now again be redirected to the login page of the IDP.
Congratulation. Single Logout is working!

 Limitations

The WS-Federation standard does not require from any application to provide a "logout image" at the logout URL. This has just shown to be best practice. However if the logout URL of an application does not provide an image, the confirmation page will show a broken image, even thou the logout was most likely successful.

The Single Logout implementation for Fediz is currently not able to delegate a logout request to the requestors IDP. So for example if the user is not authenticated at realm-a but at realm-b instead, the IDP does not forward the wsingout action to realm-b. Thus the user will only be logged of at applications in realm-a but the user still remains an active session in realm-b.

Hopefully a global logout will be supported by Fediz in the future as well.

3 comments:

  1. HI,

    Great article and it works for me - almost. I can se, that the IDP server works fine with no errors. But when I call the fedizservice, I get this error:

    SEVERE: Servlet.service() for servlet [FederationServlet] in context with path [/fedizhelloworld] threw exception
    javax.xml.ws.soap.SOAPFaultException: Problem writing SAAJ model to stream: RequireClientCertificate
    is set, but no local certificates were negotiated. Is the server set to ask for client authorization?

    The fedizservice and the fedishelloworld is deployed to the same server.

    Do you have any idears for this?

    regarrds,
    Kristian

    ReplyDelete
    Replies
    1. Hi Kristian,

      I would guess that you did not copy the ststrust.jks keystore to your tomcat root folder. The location of your sts truststore can be configured within the fediz_config.xml.

      Regards,
      Jan

      Delete
  2. Hi Jan

    I followed this tutorial to Single logout but when i try to test single sign on i get the following error on my Fediz-IDP and it keeps on asking login information. i tried to google around this issue and added the certificate to java key store.

    sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

    ReplyDelete