Delegated Authentication Integration Library

Overview

This document is targeted towards developers writing a custom portlet that will support Shibboleth Delegated Authentication. For an "out of the box" solution look at using the Web Proxy Portlet with Delegated SAML Authentication

The delegated-saml-authentication project is a library that handles most of the complexities around delegated Shibboleth authentication for the portlet developer. The library provides a specially configured HttpClient from Commomns Http-Components 4.x that can be used to make requests to a remote service that will automatically use delegated authentication.

You must complete Configuring uPortal to pass the SAML Assertion before any of these steps will be useful in a portlet.

Also a review of the Shibboleth Portal documentation is recommended for an understanding of how the process works under the hood.

Project Configuration

Step 1 - Configuring User Attributes

Portlets must declare names of all person attributes they will need to access, this is mandated by the portlet specification. Portlets list the names of the attributes they will need at runtime in their deployment descriptor, which is the file called portlet.xml. Below is a fragment of the portlet deployment descriptor configured to require SAML assertions and IdP public keys. Even though there may be multiple IdP public keys present, they will appear to the portlet as a single encoded string. The string is not encrypted. It's only Base64-encoded to be compliant with HTTP specifications.

<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
	version="1.0">

  <portlet>
  <!-- ... -->
  </portlet>

  <user-attribute>
    <description>SAML Assertion</description>
    <name>samlAssertion</name>
  </user-attribute>

  <user-attribute>
    <description>IdP Public Keys</description>
    <name>idpPublicKeys</name>
  </user-attribute>

</portlet-app>

Step 2 - Configuring Project Dependencies

If the portlet is using Maven for build management add the following in the pom.xml

<dependency>
    <groupId>org.jasig.service</groupId>
    <artifactId>delegated-saml-authentication</artifactId>
    <version>1.1.0</version>
</dependency> 

If the portlet is not using Maven the JAR can be downloaded from the Maven repository manually: http://repo1.maven.org/maven2/org/jasig/service/delegated-saml-authentication/1.1.0/

Usage

Introduction

The reason this library exists is to facilitate delegated SAML authentication for portlets that act on behalf of the logged on user. It is not the library's responsibility, nor was it its requirement, to retrieve any resources from the SAML-protected WSP. The library uses Apache HttpClient component. The only difference from using HttpClient directly is that instead of creating an instance of HttpClient, a developer obtains it from the library. The initial testing of this library was performed with only HTTP GET as the initial request to retrieve a protected resource. This is because that was the only method that would handle SAML authentication "as-needed." Support to preserve HTTP POST form post elements was recently added to the Shibboleth SP, but this will need to be tested in the future.

There are some things to keep in mind about this:

  • This resource in the initial HTTP GET can exist only for the purpose of completing authentication, and it could be ignored by the portlet.
  • Upon successful authentication, the portlet may use the Apache Commons HTTP Client API and will not be limited to HTTP GET.

Simple Example

With SAML assertion and resource URL available, the portlet is ready to start a delegated SAML authentication session. It first creates a new copy of the SAMLSession class and passes the SAML assertion as the constructor parameter. From the instance of SAMLSession, the portlet obtains an instance of HttpClient and uses the API of the Apache HttpClient from then on.

The delegated SAML authentication business logic is encapsulated in a class called SAMLDelegatedAuthenticationService. There is no need to make an explicit instance of this class, as it is created internally to the library. Here is an illustration of the steps required to retrieve a protected resource using HTTP GET:

protected String getAssertion(PortletRequest request) {
    Map userInfo = (Map) request.getAttribute(PortletRequest.USER_INFO);
    String samlAssertion = (String) userInfo.get("samlAssertion");
    return samlAssertion;
}

protected String getIdPPublicKeys(PortletRequest request) {
    Map userInfo = (Map) request.getAttribute(PortletRequest.USER_INFO);
    String idpPublicKeys = (String) userInfo.get("idpPublicKeys");
    return idpPublicKeys;
}

//Gets or creates the Shibbolized HttpClient, caching the created version in the user's session
protected HttpClient getHttpClient(PortletRequest request) {
    PortletSession portletSession = request.getPortletSession();
    HttpClient client = (HttpClient)portletSession.getAttribute(HTTP_CLIENT_ATTR);
    if (client != null) {
        return client;
    }

    String samlAssertion = getAssertion(request);

    //Configure the connection manager that should be used by the delegated auth integration code
    HttpParams params = new BasicHttpParams();
    ClientConnectionManager clientConnectionManager = this.createClientConnectionManager(request, params);

    //Create the SAMLSession, providing it the assertion and the HttpClient configuration
    SAMLSession samlSession = new SAMLSession(samlAssertion, clientConnectionManager, params);
    samlSession.setPortalEntityID(portalEntityID);   // Set the portal's entityID.  The library must provide it to the IdP.

    //If the SP private key and cert are configured provide them to the SAMLSession
    if (spPrivateKey != null && spCertificate != null) {
        samlSession.setIdPClientPrivateKeyAndCert(spPrivateKey, spCertificate);
    }

    //If the IdP's public keys were provided pass them on to the SAMLSession
    String idpPublicKeys = getIdPPublicKeys(request);
    if (idpPublicKeys != null) {
        samlSession.setIdPServerPublicKeys(idpPublicKeys);
    }

    client = samlSession.getHttpClient();
    portletSession.setAttribute(HTTP_CLIENT_ATTR, client);
    return client;
}

The following is an example of using the Shibbolized HttpClient

private void getResource(PortletRequest request) {
    String url = determineUrl(request); //portlet specific logic to figure out where to make the request to+

    HttpClient client = getHttpClient(request);
    HttpGet method = new HttpGet(resource.getResourceUrl());
  
    // There is no need to check the HTTP response status because the HTTP
    // client will handle normal HTTP protocol flow, including redirects
    // In case of error, HTTP client will throw an exception
    ResponseHandler<String> responseHandler = new BasicResponseHandler();
    String response;
    try {
       response = client.execute(method, responseHandler);
       //Do Something Portlet Specific with the response
    }
    catch (Exception ex) {  // ClientProtocolException or IOException
        log.error("Exception while trying to retrieve the resource.", ex);
    }
    finally {
        //Cleanup after the HttpClient request
        response.getEntity().consumeContent();
    }
}

In this simplest of examples, assuming that SAML protects a RESTful Web Service, at the end of this sequence the portlet may call getResource(), which will return a String representation of what came back from the WSP. Since the data encapsulating classes are stateful, SAMLSession and Resource should not be considered thread-safe.

IdP Resolution

While the example above shows how the WSP is identified with the resource URL, it would be a valid question to ask how the library locates the IdP. After all, only an IdP may issue an assertion that is suitable for the WSP. While it would be possible to require that the portlet identify the IdP, it should not be necessary. Not in all cases, anyway.

The SAML assertion issued to the portal, the same assertion that the portlet uses as a constructor parameter when creating an instance of the SAMLSession class, contains an Issuer element. This identifies the IdP that issued the assertion. The IdP is identified by what's referred to as entityID. This may sometimes look like a URL, but it does not have to be. A fully-qualified URL is required by the library to obtain a delegated SAML assertion from the IdP and present to the WSP. In order to make this flexible, the library uses a "pluggable" resolver. The resolver works with SAMLSession and an instance of the DelegatedSAMLAuthenticationState class, which is populated with the IdP's entityID. The IdP resolver will resolve the specific URL, or "endpoint." On successful resolution, the resolver places the IdP endpoint into the DelegatedSAMLAuthenticationState class. The resolver needs to implement a simple interface IdPEPRResolver:

public interface IdPEPRResolver {
  /**
   * This method will take the samlSession's idp entity ID and resolve it to
   * an endpoint.  The endpoint is a URL that the ECP will use to ask the IdP
   * for a delegated authentication assertion.  The endpoint will be placed into
   * authnState for later use.  This method is invoked immediately prior
   * to making a connection to the IdP.  The implementation of this method should
   * retrieve the IdP entityID, or name, by calling {@link SAMLSession#getIdp()}
   * and store the resolved endpoint by calling {@link DelegatedSAMLAuthenticationState#setIdpEndpoint(String) SAMLSession.setIdpEndpoint}.
   * 
   * @param samlSession SAMLSession instance
   */
  public void resolve(SAMLSession samlSession, DelegatedSAMLAuthenticationState authnState);
}

"EPR" in IdPEPRResolver stands for End Point Reference. The implementation of this interface delivered with this library assumes that Shibboleth IdP was used to issue the SAML assertion, and Shibboleth IdP includes an EPR in the assertions it issues. When working with other SAML IdP, portlet developers should write their own implementation of the above interface and set it with a authnState.setIdpResolver method call. SAMLSession defaults to using the included implementation, so at installations using Shibboleth IdP there is no need to instantiate or sent the IdP resolver in the SAMLSession object.

Reusing SAMLSession

SAMLSession encapsulates a great deal of properties. Most of them may not even be useful to portlet developers, but many are. One of the properties is the HttpClient instance that can be reused for "stateful" communications with the WSP. HttpClient from Apache Commons has the default behavior like that of a browser. This means that once a client is set up, it will continue operating in a "stateful" way, including sending cookies that were previously set by the server. These cookies may represent the SAML session and some sort of WSP-specific session. Maintaining these sessions is critical to efficiently communicating with the WSP and not performing authentications unnecessarily.

To reuse the stateful HttpClient, a portlet developer needs to make sure to use the samlSession.getHttpClient method.

Authenticating the IdP

While the library does not enforce it, communicating with the IdP should always be performed over a secure connection, most commonly utilizing TLS. In addition to encrypting all of the information between the IdP and its client, TLS also supports methods of authenticating the server. This can go a long way to assuring that there client, in this case the delegated SAML authentication library, is not a victim of a man-in-the-middle attack. The library supports two different methods of authenticating the IdP:

  • By only trusting an IdP that presents an X.509 certificate that's in a specified Java TrustStore
  • By only trusting an IdP whose certificate contains a specific public key.

By default, Java will trust any server whose certificate was signed by a recognized Certificate Authority (CA). This is also the default trust model employed by Web browsers. However, with the IdP it may be desirable to disable the default behavior and restrict which X.509 certificates are to be trusted. A clever adversary could, for example, obtain an X.509 server certificate that's signed by one of the CAs that Java is set up to trust. For that reason, the portlet developer has an option to override the default Java TrustStore. To do that, the developer can make the following call prior to the initial samlSession.getHttpClient() call:

samlSession.setIdPClientTrustStore(keyStore, keyStorePass);

The keyStore argument is a String containing the file name of the Java KeyStore, and keyStorePass is a String containing the KeyStore password. This will replace the default Java behavior of using $JAVA_HOME/jre/lib/security/cacerts as the default TrustStore. It is up to the developer to create the Java KeyStore to be used here.

Another method to authenticate the IdP is to only connect to an IdP that presents an X.509 certificate containing a specific public key. With Shibboleth SP, the IdP public key can be supplied at runtime, so there is no need to create and maintain a Java KeyStore. Portlet developers using uPortal can configure uPortal to supply the IdP public key to the portlet as a Base64-encoded String. With that String, the developer tells the library to only communicate with an IdP if it presents a trusted public key with this call:

samlSession.setIdPServerPublicKeys(encodedKey);

At this time the library only supports a single public key to trust. Internally, the library decodes the key, retrieves all the X.509 certificates presented by the IdP, and looks for a matching public key in one of those certificates. A secure connection to the IdP will only be allowed if a matching public key is found.

Authenticating to the IdP

Just as the IdP client needs to authenticate the IdP it is about to communicate with, the IdP may want to restrict the clients authorized to obtain delegated SAML authentication. This is supported by the library, and it also uses TLS as the means of IdP client authentication. The IdP authenticates its clients by examining the client X.509 certificate. By presenting to the TLS/SSL layer a certificate and private key, the client's certificate with the public key is presented to the IdP. Since, as far as the IdP is concerned, the library/portlet/portal are synonymous with their corresponding SP, the library must use the SP's certificate and private key. There are two methods by which a portlet developer may convey the private key and certificate to the TLS layer:

  • With a Java KeyStore containing the certificate and private key
  • With a PEM-encoded private key file and PEM-encoded certificate file

The first method requires a KeyStore password. The portlet developer may convey the KeyStore information to the library with the following call:

samlSession.setIdPClientKeystore(keyStore, keyStorePass);

where keyStore is the file name of the Java KeyStore and keyStorePass is the KeyStore password. Since the SP is probably going to use PEM-encoded certificate and private key, the KeyStore used in this example must be created. One advantage of using this method is that Apache HTTP Client uses KeyStore "natively," so this method may be slightly more efficient. Another potential advantage is that, since the KeyStore holding the private key must have a password, this KeyStore has that additional level of security.

The second method of conveying the private key and certificate to the library to authenticate itself to the IdP is to provide PEM-encoded files containing both. These should be the same files installed on the portal's SP, which is the same SP that received the original SAML assertion and made it available to the portal. Since these files are not password-protected, care needs to be taken when dealing with them. Some administrators may be reluctant to copy these files, but if the portal's application server resides on the same server as its SP, the administrator may simply give a permission to read these files to the application server process. Obviously, these issues are installation-specific.

Portlet developer may convey the locations of the PEM-encoded private key and certificate files like this:

samlSession.setIdPClientPrivateKeyAndCert(pkFile, certFile);

where pkFile is the file name of the PEM-encoded private key and certFile is the file name of the PEM-encoded certificate file, and they both must be readable by the portal's application server process.

The library will use the keys and/or certificates in its communications with the IdP.

Authenticating the WSP

Just as with the IdP, communications with the WSP may need to be secured using TLS. The WSP will present its certificate to the client, and the client must trust the certificate to allow the connection to proceed. The default behavior is to trust servers whose X.509 certificates have been signed by a recognized CA. Institutions wishing to issue their own certificates, either signed by their own CA or even self-signed certificates, may override the default behavior of Java and Apache HTTP Client. This can be done by providing an alternate TrustStore for communications with the WSP. The following call specifies the TrustStore to use:

resource.setWSPClientTrustStore (trustStore, trustStorePass);

where trustStore is a file name of a Java KeyStore containing certificates to trust and trustStorePass is the password that was used to secure the KeyStore. The TrustStore may contain certificates of CA, if the WSP has a CA-signed certificate or a certificate of the WSP, if the WSP uses a self-signed certificate. Please note that with this authentication the use of the Resource class becomes a requirement. The Resource class encapsulates the state specific to the WSP, while SAMLSession's state is IdP-specific.

Authenticating to the WSP

As with the IdP, the WSP may require a client TLS certificate to authenticate and trust its clients. The library supports that with TLS client certificates. This is virtually identical to the TLS client certificates that can be presented to the IdP, so rather than repeat that section verbosely, here are the two different method calls to convey the client certificate location to the library:

resource.setWSPClientPrivateKeyAndCert (pkFile, certFile);

resource.setWSPClientKeystore (keyStore, keyStorePass);

where the first method has the file names of the PEM-encoded private key and certificate as arguments and the second has the Java KeyStore location and KeyStore password as arguments.

Because the library "steps out of the way" allowing the portlet to communicate directly with the WSP, to activate the WSP authentication a portlet developer must perform an additional step prior to communicating with the WSP. This step sets up the TSL layer. This is accomplished with the following sequence:

resource = new Resource();
resource.setResourceUrl(url);  //obtained elsewhere
resource.setWSPClientPrivateKeyAndCert (pkFile, certFile);  //one of the methods to authenticate the client to the WSP
resource.setupWSPClientConnection(samlSession);  // This looks for "https" in the Resource URL and the optional port number to use
HttpClient client = samlSession.getHttpClient();

Error Handling

This library acts as a part of security infrastructure. It normally works "behind the scenes" as a communication conduit between the portal, portlet, WSP, and IdP. The errors encountered and produced by this library will virtually always be of "infrastructural" nature, and neither the portlet nor portlet's user will be able to do much to correct them. For example, the portlet may be misconfigured, the IdP may be down, or the portal may keep its session active even after the SAML session has expired. Because of this, the portlet sets all of its error conditions using a runtime exception, DelegatedAuthenticationRuntimeException. This means that no explicit error handling is required to handle this exception. The portlet container will catch these exceptions and deal with them in its own standard manner.

However, portlet developers who wish to provide their own error handling may explicitly catch DelegatedAuthenticationRuntimeException and react as desired. This exception will most likely occur on the first attempt to use the "instrumented" Apache HttpClient. If applicable, DelegatedAuthenticationRuntimeException may wrap the exception that caused the error. DelegatedAuthenticationRuntimeException will always contain its own error message even if there is a chained exception that contains its error message.