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.
<module ref="rampart"/> <parameter name="OutflowSecurity"> <action> <items>Timestamp SAMLTokenSigned </items> <signatureParts> {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;</signatureParts> <samlPropFile>saml.properties</samlPropFile> <signatureKeyIdentifier>DirectReference</signatureKeyIdentifier> </action> </parameter> <parameter name="InflowSecurity"> <action> <items>Timestamp</items> <enableSignatureConfirmation>false</enableSignatureConfirmation> <parameter name="enableSignatureConfirmation" value="false"/> </action> ´ </parameter>
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 <code>SAMLAssertion</code>. * <p/> * <p/> * A complete <code>SAMLAssertion</code> 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.
The sample project is available from https://www.sdn.sap.com/irj/scn/go/portal/prtroot/docs/library/uuid/904d7a0d-5a41-2c10-f5a7-a874ea688b21.
Using the wsdl2java tool, generate the web service proxy using the WSDL file above and invoke the wsdl2java by:
wsdl2java.bat -uri <path to wsdl file>
This will generate a src directory with the proxy classes.
Then create a program invoking this client and configuring Axis to use Rampart for security processing as in the example above.
I created a project with an ANT file for generating the proxy files, compiling and executing the proxy. To run the examples, please edit the cfg.properties file and ensure the data is adapted:
axis.home=<path to axis directory, i.e. C:/dev/axis/axis2-1.4.1/>
tomcat=<path to tomcat, i.e. C:/dev/axis/apache-tomcat-6.0.18>
ws.url.=<ws endpoint url from wsdl file>
To run the example, call the command below. This will generate the proxy based on the WSDL, compile it and make a ws call.
ant run
Hint: Adapting the example for your ABAP environment
The example ant file takes the wsdl file, generates a proxy from the WSDL file and compiles the code. When adopting this example to your environment, the example ws may be different. Make the following changes to your environment:
1) replace the wsdl file
2) change the coding in file CallProxy.java, which is invoking the axis2 proxy.
Example 2: Running Axis2 in an application server
Based on example 1, the scenario is enhanced by running the proxy from a web application hosted on an application server. The web application contains a servlet requiring authentication. The user identified by the authentication is then used for the Subject in the SAML assertion.
The sample project is available from https://www.sdn.sap.com/irj/scn/go/portal/prtroot/docs/library/uuid/904d7a0d-5a41-2c10-f5a7-a874ea688b21.
In my example, I'll use Tomcat 6 for hosting the web application. To run the example, copy all library files (including the updated wss4j version 1.5.7) to the lib directory of Tomcat. Assuming the cfg.properties has already been adopted, run the command below to create a war file.
ant build
This will create a file saml.war. Deploy this, i.e. using the /manager application. To run the example, callhttp://host:port/saml.
Example 1 is enhanced by using a servlet which requires authentication with a user in the role manager. In the servlet, the name of the currently authenticated principal is obtained and used for the ws call.
String res = CallProxy.callProxy("abc", url, repository, axis2, request.getUserPrincipal().getName());
If you compiled the example, the defaults fro axis2 installation and tomcat home should be ok. After authentication with a user in the manager role, you receive a response code like:
Configuration: Web Service Endpoint: http://localhost:57400/sap/bc/srt/rfc/sap/wsse_echo/001/saml/saml Axis repository: C:/dev/axis/axis2-1.4.1/repository Axis2 configuration file: C:/dev/axis/apache-tomcat-6.0.18/webapps/saml/WEB-INF/classes/conf/webcontainer/axis2.xml Authenticated user: DEBOER WS CallCall ok. Response: U7C 001 DEBOER E Response: abc