Single Sign on using SAML with Apache Axis2 (Web Service Runtime)

Axis

Axis2 is a Java based open source web service runtime. It consists of tools for generating a Java proxy based from a WSDL service description. The web service proxy is used for invoking web services, as well as tools for generating web services on the provider side.

Checking SAP Notes

SAML Sender-vouches is supported with releases AS ABAP 7.00 (SP 15) and higher. Please ensure the following SAP notes have been applied:
AS ABAP 7.00:
  • SAP Notes: 1176558, 1325457
  • Kernel Patch level: 207
AS ABAP 7.01:
  • Support Package SP5
  • Kernel patch level: 74
AS ABAP 7.10:
  • SAP Notes 1170238, 1325457
  • Kernel patch level: 150

Checking Axis versions

I used the following library to run this example:
1) Axis 1.4.1 from http://ws.apache.org/axis2/download.cgi
2) Wss4J 1.5.7  from http://www.apache.org/dyn/closer.cgi/ws/wss4j/
Due to a bug in wss4j version 1.5.4 shipped with Axis2 1.4.1, I replaced the wss4j with version 1.5.7.  wss4j 1.5.4 ignores the SignedParts elements in axis2.xml and does not sign the timestamp element.

Configure the provider 

The ws provider needs to be configured to SAML Sender-Vouches authentication. To create such a configuration, follow the  instructions.

Configure Trust between Axis2 and SAP WebAS ABAP

The scenario involves an XML Signature. If you already have a certificate for signing the messages, feel free to use it. Otherwise, create a certificate with the java keytool by invoking the commands below (passwords are only as an example):

Create the keypair
keytool -genkey -alias SAML -keyalg RSA -keysize 1024 -validity 1000 -keypass abcd1234 -storepass abcd1234 -keystore axis.jks
Export the key
keytool -export -file axis.crt -alias SAML -keypass abcd1234 -storepass  abcd1234 -keystore axis.jks
Any SAML assertion created by Axis2 needs to be trusted by the SAP system and be mapped to an SAP user. Please follow the instructions from section Configure Trust for SAML SenderVouches authentication ( ABAP) using the following information:
  • SAML Issuer: Axis
  • SAML Name Identifier: (empty,not used)
  • Subject of the X.509 certificate used for the message signature (from the example): CN=Axis, OU=NW SIM, O=NW, L=Walldorf, SP=Baden Wuerttemberg, C=DE
The name of the issuer is kept in the Axis2 configuration file saml.properties
saml.properties
org.apache.ws.security.saml.issuerClass=saml.SAPSAMLIssuerImpl
org.apache.ws.security.saml.issuer.cryptoProp.file=crypto.properties
org.apache.ws.security.saml.issuer.key.name=SAML
org.apache.ws.security.saml.issuer.key.password=abcd1234
saml.issuer=Axis
saml.validity=200
org.apache.ws.security.saml.authenticationMethod=password
The second file crypto.properties contains the configuration information for the keystore
crypto.properties 
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=abcd1234
org.apache.ws.security.crypto.merlin.keystore.alias=SAML
org.apache.ws.security.crypto.merlin.file=keys/axis.jks

Create the consumer

From the service configuration created in the previous step, copy the WSDL url and open it in the browser. By default the SAP WSDL contains WS-Policy. Axis is not able of processing these assertions, therefore it is best to take the WSDL without policy. Obtain the WSDL without policy by replacing ws_policy with standard in the WSDL url, i.e.:
With WS-Policy
http://host:port/sap/bc/srt/wsdl/bndg_001560AB336002ECB9B230CE92A94CD0/wsdl11/allinone/ws_policy/document?sap-client=001
Without WS-Policy
http://host:port/sap/bc/srt/wsdl/bndg_001560AB336002ECB9B230CE92A94CD0/wsdl11/allinone/standard/document?sap-client=001
Save the WSDL in a file.

Configure Axis2 to issue SAML assertions

The axis2.xml configuration file must configure a SAML assertion, a wsu:TimeStamp and a Signature over SOAP Body, wsu:TimeStamp and SAML assertion in the request and a TimeStamp in the response. This is configured by the following piece of XML.
      "rampart"/>
      "OutflowSecurity">
            
                   Timestamp SAMLTokenSigned
                   {Content}{http://schemas.xmlsoap.org/soap/envelope/}Body;{Content}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;
                   saml.properties
                   DirectReference
            

     

      "InflowSecurity">
            
                   Timestamp
                   false
                   "enableSignatureConfirmation"  value="false"/>
            

´             
The property file saml.properties contains the SAML specific configuration. Ramparts default implementation for creating SAML assertions does not define the validity of the SAML assertion, which is required by SAPs implementation. Use the example implementation below to generate SAML assertions accepted by SAP. The response contains a timestamp, which is configured in the InflowSecurity section.
 package saml;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Properties;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.components.crypto.CryptoFactory;
import org.apache.ws.security.saml.SAMLIssuer;
import org.opensaml.SAMLAssertion;
import org.opensaml.SAMLAuthenticationStatement;
import org.opensaml.SAMLException;
import org.opensaml.SAMLNameIdentifier;
import org.opensaml.SAMLStatement;
import org.opensaml.SAMLSubject;
import org.w3c.dom.Document;
/**
 * Builds a WS SAML Assertion supported by SAP AS ABAP/Java
 *
 * @author Martijn de Boer
 */
public class SAPSAMLIssuerImpl implements SAMLIssuer {
 private SAMLAssertion samlAssertion = null;
 private Properties properties = null;
 private Crypto issuerCrypto = null;
 private String issuerKeyPassword = null;
 private String issuerKeyName = null;
 private String username;
 /**
  * Constructor.
  */
 public SAPSAMLIssuerImpl() {
  System.err.println("Error: no cfg properties passed");
 }
 public SAPSAMLIssuerImpl(Properties prop) {
  /*
   * if no properties .. just return an instance, the rest will be done
   * later or this instance is just used to handle certificate conversions
   * in this implementation
   */
  if (prop == null) {
   return;
  }
  properties = prop;
  String cryptoProp = properties.getProperty("org.apache.ws.security.saml.issuer.cryptoProp.file");
  if (cryptoProp != null) {
   issuerCrypto = CryptoFactory.getInstance(cryptoProp);
   issuerKeyName = properties.getProperty("org.apache.ws.security.saml.issuer.key.name");
   issuerKeyPassword = properties.getProperty("org.apache.ws.security.saml.issuer.key.password");
  }
 }
 /**
  * Creates a new SAMLAssertion.
  *
  *
  * A complete SAMLAssertion is constructed.
  *
  * @return SAMLAssertion
  */
 public SAMLAssertion newAssertion() { // throws Exception {  // Issuer must enable crypto functions to get the issuer's certificate  String issuer = properties.getProperty("saml.issuer");
  int validity = Integer.parseInt(properties.getProperty("saml.validity", "300"));
  String qualifier = "";
  try {
   SAMLNameIdentifier nameId = new SAMLNameIdentifier(username, qualifier, "");
   nameId.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
   String subjectIP = null;
   String authMethod = null;
   if ("password".equals(properties.getProperty("org.apache.ws.security.saml.authenticationMethod"))) {
    authMethod = SAMLAuthenticationStatement.AuthenticationMethod_Password;
   }
   Date authInstant = new Date();
   SAMLSubject subject = new SAMLSubject(nameId, Arrays.asList(new String[] { SAMLSubject.CONF_SENDER_VOUCHES }), null, null);
   SAMLStatement[] statements =
{ new SAMLAuthenticationStatement(subject, authMethod, authInstant, subjectIP, null, (Collection) null) };
   Date now = new Date();
   Date expires = new Date();
   expires.setTime(now.getTime() + validity * 1000);
   samlAssertion = new SAMLAssertion(issuer, now, expires, null, null, Arrays.asList(statements));
  } catch (SAMLException ex) {
   throw new RuntimeException(ex.toString(), ex);
  }
  return samlAssertion;
 }
 /**
  * @param userCrypto
  *            The userCrypto to set.
  */
 public void setUserCrypto(Crypto userCrypto) {
  // ignored for sender vouches }
 /*
  * ignored (non-Javadoc)
  *
  * @see org.apache.ws.security.saml.SAMLIssuer#setUsername(java.lang.String)
  */
 public void setUsername(String username) {
  this.username = username;
 }
 /**
  * @return Returns the issuerCrypto.
  */
 public Crypto getIssuerCrypto() {
  return issuerCrypto;
 }
 /**
  * @return Returns the issuerKeyName.
  */
 public String getIssuerKeyName() {
  return issuerKeyName;
 }
 /**
  * @return Returns the issuerKeyPassword.
  */
 public String getIssuerKeyPassword() {
  return issuerKeyPassword;
 }
 /**
  * @return Returns the senderVouches.
  */
 public boolean isSenderVouches() {
  return true;
 }
 /*
  * ignored (non-Javadoc)
  *
  * @see
  * org.apache.ws.security.saml.SAMLIssuer#setInstanceDoc(org.w3c.dom.Document
  * )
  */
 public void setInstanceDoc(Document instanceDoc) {
  // ignored for sender vouches }
}

Invoking a web service using Axis2

To invoke the proxy, use the following example below. Basically the following data is needed:
  • Endpoint url of the web service
  • Path to Axis2 repository
  • Path to axis2 configuation file
  • Name of the user to write into the SAML assertion
  •  
Below is an coding example to invoke a proxy with SAML authentication. Except setting the username to be included in the SAML assertion, all data is included in configuraiton files.
package call;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.ws.security.handler.WSHandlerConstants;
import proxy.WsseEchoStub;
public class CallProxy {
 public static String callProxy(String input, String url, String repositoryDir, String axis2Path, String user)
throws Exception {
  /*
   * load configuration
   */
  ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(repositoryDir, axis2Path);
  /*
   * create proxy instance
   */
  WsseEchoStub ws = new WsseEchoStub(ctx, url);
  /*
   * Set user to write into SAML assertion
   */
  ws._getServiceClient().getOptions().setProperty(WSHandlerConstants.USER, user);
  /*
   * call web service
   */
  proxy.WsseEchoStub.WSSE_ECHO a = new proxy.WsseEchoStub.WSSE_ECHO();
  a.setINPUT(input);
  WsseEchoStub.WSSE_ECHOResponse res = ws.WSSE_ECHO(a);
  return res.getOUTPUT();
 }
}

Example 1: Axis2 standalone

For illustration purposes, I'll first show how to invoke the proxy from a standalone Java application and authenticate the service call in the ABAP stack. As the standalone application does not support authentication itself, it should only be seen as a technical example and not used in realistic scenarios.

More Here


Courtesy:http://wiki.sdn.sap.com/wiki/display/Security/Single+Sign+on+using+SAML+with+Apache+Axis2+%28Web+Service+Runtime%29