Enabling SAML Service Provider Initiated web SSO

To enable SAML Service Provider Initiated (SP-Initiated) web SSO (SSO), complete the following task.

  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 in the $WAS_HOME/dev folder.
    • The com.ibm.ws.wssecurity.saml.common.util.UTC class that is used in this sample can be found in the com.ibm.wsfp.main.jar file that is located in the $WAS_HOME/plugins folder.
    • The HttpServletRequest class that is used in this sample can be found in the $WAS_HOME/lib/j2ee.jar folder.
    The method getAuthnRequest(HttpServletRequest req, String errorMsg, String acsUrl, ArrayList<String> ssoUrls) must return a map that includes four entries with the following keys.
    AuthnRequestProvider.SSO_URL
    The SAML identity provider's SSO URL.
    AuthnRequestProvider.RELAY_STATE
    The relayState as defined by the SAML Web Browser SSO profile.
    AuthnRequestProvider.REQUEST_ID
    The value for this key must match the ID attribute's value in the AuthnRequest message.
    AuthnRequestProvider.AUTHN_REQUEST
    A Base 64 encoded AuthnRequest message as defined in the spec.
    Sample authentication request for SAML SSO.
    package com.ibm.saml;
    
    import java.security.SecureRandom;
    import java.util.ArrayList;
    import java.util.Base64;
    import java.util.Date;
    import java.util.HashMap;
    
    import javax.servlet.http.HttpServletRequest;
    
    import com.ibm.websphere.security.NotImplementedException;
    import com.ibm.ws.wssecurity.saml.common.util.UTC;
    import com.ibm.wsspi.security.web.saml.AuthnRequestProvider;
    
    /**
    SAML authentication request provider implementation
    */
    public class SAMLAuth implements AuthnRequestProvider {
    
    	private static String printableChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    
    	public HashMap<String, String> getAuthnRequest(HttpServletRequest req, String errorMsg, String acsUrl,
    			ArrayList<String> ssoUrls) throws NotImplementedException {
    
    		System.out.println("Create SAML AuthnRequest \n Date :: "+new Date());
    		HashMap<String, String> map = new HashMap<String, String>();
    
    		// ADFS server url
    		String ssoUrl = "https://adfsserver.mdmce.local/adfs/ls";
    		map.put(AuthnRequestProvider.SSO_URL, ssoUrl);
    		System.out.println("ssoUrl:: "+ssoUrl);
    		
    		String reqURI = req.getRequestURI();
    		System.out.println("RequestURI:: "+reqURI);
    		System.out.println("ssoUrls:: "+ssoUrls);
    		
    		String scheme = req.getScheme();             // http
    	    String serverName = req.getServerName();     // hostname.com
    	    int serverPort = req.getServerPort();        // 80
    	    String contextPath = req.getContextPath();   // /mywebapp
    	    
    	    String relayState = generateRandom();
    	    // If the URL is ACS URL then do not set relayState parameter with constructed URL
    	    if(!reqURI.contains("samlsps") && !reqURI.contains("error")) {
    	    	StringBuilder url = new StringBuilder();
    		    url.append(scheme).append("://").append(serverName);
    		    if (serverPort != 80 && serverPort != 443) {
    		        url.append(":").append(serverPort);
    		    }
    		    url.append(contextPath);
    		    System.out.println("URL:: "+url.toString());
    		    relayState=url.toString();
    	    }
    	    
    		map.put(AuthnRequestProvider.RELAY_STATE, relayState);
    		System.out.println("RelayState:: "+relayState);
    		
    		String requestId = generateRandom();
    		map.put(AuthnRequestProvider.REQUEST_ID, requestId);
    		System.out.println("RequestId:: "+requestId);
    		
    		String acsURL = "https://<ipaddress>:<portnumber>/samlsps/ipm";
    		String issuer = acsUrl;
    		String destination = ssoUrl;
    		System.out.println("acsURL:: "+acsURL);
    
    		// create AuthnRequest- Authentications methods
    		// urn:federation:authentication:windows
    		// urn:oasis:names:tc:SAML:2.0:ac:classes:Password
    		// urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos
    		String authnMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
    				+ "<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" " + "ID=\"" + requestId
    				+ "\" Version=\"2.0\" " + "IssueInstant=\"" + UTC.format(new java.util.Date())
    				+ "\" ForceAuthn=\"true\" IsPassive=\"false\""
    				+ " ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" "
    				+ "AssertionConsumerServiceURL=\"" + acsURL + "\" " + "Destination=\"" + destination + "\"> "
    				+ "<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:emailAddress\"" + " SPNameQualifier=\"mysp\""
    				+ " AllowCreate=\"true\" /> <samlp:RequestedAuthnContext Comparison=\"exact\"> "
    				+ "</samlp:RequestedAuthnContext> </samlp:AuthnRequest>";
    
    		System.out.println("Before encoding authnMessage :"+authnMessage);
    		String encodedAuth = Base64.getEncoder().encodeToString(authnMessage.getBytes());
    		System.out.println("After encoding authnMessage :"+encodedAuth);
    		
    		map.put(AuthnRequestProvider.AUTHN_REQUEST, encodedAuth);
    		return map;
    
    	// Implement method to generate a random alpha numeric String that is unique
    	// each time it is invoked and cannot be easily predicted (like a counter)
    	private String generateRandom() {
    		
    		byte[] seed = SecureRandom.getSeed(16);
    		SecureRandom random = new SecureRandom(seed);
    		int modder = printableChars.length();
    		char[] result = new char[8];
    		for (int i = 0; i < 8; i++) {
    			int j = random.nextInt(modder);
    			result[i] = printableChars.charAt(j);
    		}
    		return new String(result);
    	}
    
    	@Override
    	public String getIdentityProviderOrErrorURL(HttpServletRequest arg0, String arg1, String arg2,
    			ArrayList<String> arg3) throws NotImplementedException {
    		return null;
    	}
    }
    
    Note: If the SSO parameters section in the app_secrets.yaml file does not have SAML assertion, then the default value of the forceAuthn parameter is false.
  2. In the custom class you can specify either urn:oasis:names:tc:SAML:2.0:ac:classes:Password as authentication context for password authentication OR urn:federation:authentication:windows for windows-based authentication.
  3. Put a JAR file that contains your custom class in the $WAS_HOME/lib/ext folder.
  4. Copy the com.ibm.wsfp.main.jar file in the $WAS_HOME/lib/ext folder. The location of this JAR file is $WAS_HOME/plugins.
    Note: For multiple node clusters, perform steps 3 and 4 above for all the nodes in the cluster.
  5. Configure the SAML web SSO TAI to use your AuthnRequest message.
    1. Log in to WebSphere® Application Server 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. Under Custom properties, set the following property for the login error page.
      • Name: sso_<id>.sp.login.error.page
        Value: Fully qualified name of AuthnRequestProvider implementation class
        Example
        com.ibm.saml.SAMLAuthRequest
      • Name: sso_1.sp.acsUrl
        Value: https://<hostname>:<sslport>/samlsps/<any URI pattern string>
        Where hostname is the hostname of the system where the WebSphere Application Server is installed and sslport is the web server SSL port number (WC_defaulthost_secure).
        Example
        https://host.ipm.in:9443/samlsps/ipm
  6. Set the following custom properties according to your configuration.
    Name Value
    sso_1.sp.acsUrl https://ipmserver.ipm.in:9443/samlsps/ipm
    sso_1.sp.idMap idAssertion
    sso_1.idp_1.EntityID http://adfsserver.ipm.local/adfs/services/trust
    sso_1.sp.preventReplayAttackScope Server
    replayAttackTimeWindow 600
    sso_1.sp.preventReplayAttack False
    sso_1.sp.useRealm http://adfsserver.ipm.local/adfs/services/trust
    sso_1.sp.preserveRequestState False
    sso_1.sp.filter request-url^=/|/mdm_ui
    sso_1.sp.login.error.page com.ibm.saml.SAMLAuthRequest
    sso_1.idp_1.SingleSignOnUrl https://adfsserver.ipm.local/adfs/ls/
    Note:
    • The SingleSignOnUrl and EntityID property are automatically added when you import federation metadata file.
    • In the case of multi-node cluster deployment with a load balancer, the ACS URL should point to the load balancer URL and port.
  7. Restart the WebSphere Application Server.

What to do next

Configuring SSO in the browser.