Enabling SAML SP-Initiated web single sign-on (SSO)

By default, the WebSphere® Application Server SAML Trust Association Interceptor (TAI) supports IdP-initiated SSO. When custom code is in place, the SAML TAI can be configured to support SP-initiated SSO.

Before you begin

Ensure that your system is enabled to use the SAML web SSO feature.

About this task

This task provides an example class and the steps to configure SP-initiated SSO with HTTP POST binding. The AuthnRequest must be Base64 encoded. This example uses the Base64 encoder that is provided in the com.ibm.websphere.wssecurity.wssapi.WSSUtilFactory API. You can use any Base64 encoder that you want. For more information, see the Javadoc for the com.ibm.websphere.wssecurity.wssapi.WSSUtilFactory API.

The SAML web SSO feature supports HTTP POST bindings. It does not support HTTP Redirect binding. You might need to consider these support details when you develop your SAML authentication request provider.

Procedure

  1. Develop a SAML authentication request provider that implements the com.ibm.wsspi.security.web.saml.AuthnRequestProvider interface.
    • The com.ibm.wsspi.security.web.saml.AuthnRequestProvider class is found in the was_public.jar file, located in the (was_home)/dev directory.
    • The com.ibm.ws.wssecurity.token.UTC class used in this sample is found in the com.ibm.wsfp.main.jar file, located in the (was_home)/plugins directory.
    The getAuthnRequest(HttpServletRequest req, String errorMsg, String acsUrl, ArrayList<String> ssoUrls method must return a map that includes four entries with the following keys.
    AuthnRequestProvider.SSO_URL
    The SAML identity provider's Single-Sign-On URL.
    AuthnRequestProvider.RELAY_STATE
    The relayState as defined by the SAML Web Browser single sign-on profile.
    AuthnRequestProvider.REQUEST_ID
    The value for this key must match the ID attribute's value in the AuthnRequest message.
    AuthnRequestProvider.AUTHN_REQUEST
    A Base64 encoded AuthnRequest message. Your code is responsible for generating the AuthnRequest message that is specific to your Identity Provider (IdP) requirements.

    The following example shows a sample implementation of the AuthnRequestProvider interface. To implement this code, replace the values in the example with details from your IdP requirements.

    
    import java.util.ArrayList;
    import java.util.HashMap;
    
    import javax.servlet.http.HttpServletRequest;
    import com.ibm.websphere.security.NotImplementedException;
    import com.ibm.ws.wssecurity.token.UTC;
    import com.ibm.wsspi.security.web.saml.AuthnRequestProvider;
    import com.ibm.websphere.wssecurity.wssapi.WSSUtilFactory;
    
    public class samlSp implements com.ibm.wsspi.security.web.saml.AuthnRequestProvider {
                                                                                        
        // these values are specific to your IdP and application
        private static String ssoUrl_default = "https://idp.acme.com/saml20/login";
        private static String issuer = "https://acme.com";
        private static String nameQualifier = "aNameQualifier";
    
        public HashMap <String, String> getAuthnRequest(HttpServletRequest req, String errorMsg, 
                                                        String acsUrl, ArrayList<String> ssoUrls)
        throws NotImplementedException {
    
            // create map with following keys
            HashMap <String, String> map = new HashMap <String, String>();
    
            // the next section needs to be changed to code that is more appropriate
            // for your configuration and the request.  Do not use this code as-is.
            String ssoUrl = ssoUrl_default;
            if (ssoUrls!=null && !ssoUrls.isEmpty() && ssoUrls.size()>0)  
              ssoUrl=ssoUrls.get(0);
    
            map.put(AuthnRequestProvider.SSO_URL, ssoUrl);
    
            String relayState = generateRandom();
            map.put(AuthnRequestProvider.RELAY_STATE, relayState);
    
            String requestId = generateRandom();
            map.put(AuthnRequestProvider.REQUEST_ID, requestId);
    
            //create the AuthnRequest                         
            String authnMessage = createAuthnRequest(requestId);
    
            try {
                // get an instance of the WSSUtilFactory
                WSSUtilFactory wssuf = WSSUtilFactory.getInstance();
    
                // base64 encode the authn request string
                String encAuthnMsg = wssuf.encode(authnMessage.getBytes());
    
                // put the base64-encoded authn request in the map
                map.put(AuthnRequestProvider.AUTHN_REQUEST, encAuthnMsg);     
            } catch ( Exception  e ) {
                // this is to handle the WSSException that might thrown by WSSUtilFactory
                // do what you want here
            }
            return map;
        }
        private String createAuthnRequest(String reqId) {
            //create an Authn request that is specific to your IdP's requirements here.  
            //An example of one that does not include a signature:
            return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            +"<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" "
            +"ID=\"" + reqId + "\" Version=\"2.0\" "
            + "IssueInstant=\"" +UTC.format(new java.util.Date())+ "\" ForceAuthn=\"false\" IsPassive=\"false\""
            + "ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" "
            + "AssertionConsumerServiceURL=\"" + acsUrl +"\" "
            + "Destination=\"" + ssoUrl +"\"> "
            + "<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">"
            + issuer
            +"</saml:Issuer> <samlp:NameIDPolicy"
            +"Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:transient\""
            +"SPNameQualifier=\"" + nameQualifier + "\" "
            +"AllowCreate=\"true\" /> <samlp:RequestedAuthnContext Comparison=\"exact\"> "
            +"<saml:AuthnContextClassRef xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">"
            +"urn:oasis:names:tc:SAML:2.0:ac:classes:</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>" 
            +</samlp:AuthnRequest>";
        }
        public String getIdentityProviderOrErrorURL(HttpServletRequest arg0, String arg1, String arg2,
                   ArrayList<String> arg3) throws NotImplementedException {
            // this method is required by the IdentityProviderMapping parent interface
            // there is nothing to do here
            return null;
        }
        private String generateRandom() {
            // TODO implement code that generates a random alpha numeric String that must be
            // unique each time it is invoked and cannot be easily predicted (like a counter)
        }
    }
    
  2. Put a JAR file that contains your custom class in the (WAS_HOME)/lib/ext directory.
  3. Configure the SAML web SSO TAI to use your AuthnRequest message.
    1. Log on to the WebSphere Application Server administrative console.
    2. Click Security > Global security.
    3. Expand Web and SIP security and click Trust association.
    4. Click Interceptors.
    5. Click com.ibm.ws.security.web.saml.ACSTrustAssociationInterceptor
    6. For Custom properties, click new, then complete the following custom property information, where id is what you assigned to the SSO Service Provider (SP) for which you want this property to apply.
      Name
      sso_<id>.sp.login.error.page
      Value
      The class name of your custom AuthnRequestProvider implementation.