Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
4,232
Introduction:

This Blog will guide you to pass dynamic Signature and Timestamp in the Header of a POST Method Using REST Adapter in SAP PI. For calculating signature there are certain rules which will be mentioned in the Pre-Request Script of the Third-party service which you will be integrating into SAP. In this case, the Signature is calculated by the HMAC SHA256 algorithm and passed in the Header of the POST Service where the receiving third-party system will validate the signature.

Preface:

I recently worked with a Receiver Adapter integration with a Third-party and there was a challenge in Passing Headers. In general Integration scenarios in Headers, we used to pass Token or Authorization or some known parameters, the case which I have worked on has Signature and Timestamp to be passed in the Headers.

The calculation of Signature has some challenges where we have to use certain algorithms with some parameters. The Signature which we generate and send in the Headers will be validated by the third party and access will be provided.

It was difficult for me to implement this type of scenario and it was very hard to find blogs in such cases. So, I thought of sharing my experience through this blog.

Requirement:

Passing dynamic Signature and Timestamp in the Header of a POST Method Using REST Adapter in SAP PI

Sample POST service:


Postman Sample


In Headers of a post-service Time Stamp and Signature which has Host, Method, Request Type, and Secret Key must be kept

The Signature is calculated by HMAC SHA256 algorithm. For this algorithm to calculate the signature Request Method, Host, Method Type, Time Stamp and the Json Request Content is needed.

 

Example:

POST URL General Format: "https" /  "Host"  / "Method"

Request Content:

{

"Id": "123",

"Type": "Sample Type"

}

 

Signature Calculation parameters:

Host: Host Name

Method: Method Name

Request Method: POST

Secret Key: client Id and client secret Id encoded

Note: In the sample postman collection, which you will get for the requirement there will be a pre-request script that will give details about the signature calculation methods.

 

Configuration Steps:

Create Data Type, Message Type, Service Interface, Message Mapping, and Operation Mapping of Request and Response in ESR.

Create and Import Java Mapping Archive using the below Java Code
package sap.com;

import com.sap.aii.mapping.api.*;
import org.json.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Date;

import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class CalculateSignaturePost extends AbstractTransformation {
private List<String> array_nodes = new ArrayList<String>();

// Adding Dynamic Attributes
private static final DynamicConfigurationKey Sign = DynamicConfigurationKey
.create("http://sap.com/xi/XI/System/REST", "Signature");

private static final DynamicConfigurationKey TimeStamp = DynamicConfigurationKey
.create("http://sap.com/xi/XI/System/REST", "TimeStamp");

@Override
public void transform(TransformationInput transformationInput, TransformationOutput transformationOutput)
throws StreamTransformationException {
try {

// Getting Input Parameters - Secret Key, Host, Request Method, Method
String SecretKey = transformationInput.getInputParameters().getString("Secret_Key");
String Host = transformationInput.getInputParameters().getString("Host");
String Method = transformationInput.getInputParameters().getString("Method");
String RequestMethod = transformationInput.getInputParameters().getString("Request_Method");

InputStream inputstream = transformationInput.getInputPayload().getInputStream();
String sourcexml = "";
String targetxml = "";
String line = "";

BufferedReader br = new BufferedReader(new InputStreamReader(inputstream));

while ((line = br.readLine()) != null)
sourcexml += line + "\n";
br.close();

// Converts XML to JSON
JSONObject xmlJSONObj = XML.toJSONObject(sourcexml);

// Making the Node as Array and Converting Numbers to string
array_nodes.add("arraynode");
xmlJSONObj = handleJSONData(xmlJSONObj);

// Converts JSON to string
String jsonstring = xmlJSONObj.toString(0);


//Removing Namespace Tag
jsonstring = jsonstring
.replaceAll("\"" + "xmlns:ns0" + "\":" + "\"" + "your namespace" + "\"" + ",", "");

//Removing the outer element
jsonstring = jsonstring.replaceAll("\"" + "ns0:Message Type Name" + "\":", "");
int len = jsonstring.length();
len = len - 1;
jsonstring = jsonstring.substring(1, len);

// Encoding JSON Message string to base64
jsonstring = jsonstring.trim();
Base64 base64 = new Base64();
String jsonBase64 = new String(base64.encode(jsonstring.getBytes()));

// Getting Current TimeStamp
Long CurrentTimeStamp = new Date().getTime();
getTrace().addDebugMessage("TimeStamp = " + CurrentTimeStamp);

// Creating the string to calculate signature
String stringToSing = RequestMethod + "\n" + Host+ "\n" +Method+ "\n" + "id="
+ "&t="+CurrentTimeStamp+ "&ed=" + jsonBase64;
getTrace().addDebugMessage(stringToSing);

// Calculating signature using HMAC SHA256 algorithm
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(SecretKey.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);

// Encoding signature calculated to base64
String hash = Base64.encodeBase64String(sha256_HMAC.doFinal(stringToSing.getBytes()));
getTrace().addDebugMessage("Signature = " + hash);

// Add Signature & Timestamp as dynamic attributes
DynamicConfiguration conf = transformationInput.getDynamicConfiguration();
conf.put(Sign, hash);
conf.put(TimeStamp, String.valueOf(CurrentTimeStamp));

targetxml = jsonstring.trim();
transformationOutput.getOutputPayload().getOutputStream().write(targetxml.getBytes("UTF-8"));
} catch (Exception exception) {
getTrace().addDebugMessage(exception.getMessage());
throw new StreamTransformationException(exception.toString());
}
}

public JSONObject handleJSONData(JSONObject jsonObj) {
/*
* Parse the JSON Structure to Delete a record or convert it to an array
* Input: JSONObject -> Json Sub structure to be updated Output:
* JSONObject -> Updated Json Sub structure with deleted records and
* arrays.
*/

try {
// Create an array of keyset to loop further
String arr[] = new String[jsonObj.keySet().size()];
int k = 0;
for (String key : jsonObj.keySet())
arr[k++] = key;

// Loop through all the keys in a JSONObject
for (String key : arr) {

// If there are records to be converted to Array, convert it.
if (array_nodes.contains(key)) {
jsonObj = forceToJSONArray(jsonObj, key);
}

// If the sub node is a JSONArray or JSONObject, step inside the
// Object
if (jsonObj.get(key) instanceof JSONArray) {
JSONArray sjao = jsonObj.getJSONArray(key);
for (int i = 0; i < sjao.length(); i++) {
sjao.put(i, handleJSONData(sjao.getJSONObject(i)));
}
jsonObj.put(key, sjao);
} else if (jsonObj.get(key) instanceof JSONObject) {
jsonObj.put(key, handleJSONData(jsonObj.getJSONObject(key)));
} else {
// Convert number to String
Object val = jsonObj.get(key);
if (val instanceof Integer || val instanceof Float || val instanceof Double || val instanceof Long
|| val instanceof Short)
jsonObj.put(key, jsonObj.get(key).toString());
}
}
} catch (Exception e) {
// Handle all exceptions
if (getTrace() != null) {
getTrace().addDebugMessage("Exception while Updating Payload: ", e);
} else
e.printStackTrace();
}
return jsonObj;
}

public static JSONObject forceToJSONArray(JSONObject jsonObj, String key) throws org.json.JSONException {
/*
* Force Convert a record to JSON Array Input: 1) JSONObject -> JSON Sub
* structure to be updated 2) key -> Key whose value is to be converted
* to JSONArray Output: JSONObject -> Updated Json Sub structure with
* deleted records and arrays.
*/

// Get the key value from JSONObject using opt() and not get(), as it
// can also return null value.
Object obj = jsonObj.opt(key);

// If the obj doesn't exist inside my the JsonObject structure, create
// it empty
if (obj == null) {
jsonObj.put(key, new JSONArray());
}
// if exist but is a JSONObject, force it to JSONArray
else if (obj instanceof JSONObject) {
JSONArray jsonArray = new JSONArray();
jsonArray.put((JSONObject) obj);
jsonObj.put(key, jsonArray);
}
// if exist but is a primitive entry, force it to a "primitive"
// JSONArray
else if (obj instanceof String || obj instanceof Integer || obj instanceof Float || obj instanceof Double
|| obj instanceof Long || obj instanceof Boolean) {
JSONArray jsonArray = new JSONArray();
jsonArray.put(obj);
jsonObj.put(key, jsonArray);
}
return jsonObj;
}

}

Add the Java Mapping Archive to Operation Mapping of the Request Content.


Java Class in Operation Mapping and Binding


In the Highlighted Binding of Java Class create the Input parameters which must be passed to the Java class.


Binding Parameters


In Integration Configuration when you import the Operation Mapping in Receiver Interface you will get the option to pass the Parameters (Host, Method, Request Method, Secret Key)


Setting Parameters in Integration Configuration


The response coming from the Java Class will have two Parameters (TimeStamp and Signature). Those two parameters have to be passed in the HTTP Headers of the communication channel.


Passing Dynamic Headers in Communication Channel


The signature from the Java Mapping will be mapped to the HTTP headers and will be passed to the external service.

This brings me to the conclusion of this blog. Please feel free to comment if you require any clarifications regarding the code.

Thanks & Regards

Thamarai Selvan

SAP Technical Consultant
2 Comments
chaishree2993
Member
0 Kudos
Hi,

Thanks for the detailed blog. We had a similar requirement and we have UDF in place now But we see the Signature generated is not unique for each messages and there is a request to get unique HMAC ID combination.

Please help me know were you able to generate unique HMAC ID combination for each message or is it a standard behavior to have same ID  used for messages processed in certain time interval?

 

Thanks,

Chaitra
PRAGSMATIC
Participant
0 Kudos
I have a question. I am confused at the part where you are doing a lot of work for the json. Could you explain why you do that ?
Labels in this area