Monday, 17 January 2022

Passing Dynamic Signature (HMAC SHA256 algorithm) and Timestamp in SAP PI REST Adapter Header Using Java Mapping

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:

SAP ABAP Exam Prep, SAP ABAP, SAP ABAP Exam, SAP ABAP Career, SAP ABAP Preparation, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development
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.

SAP ABAP Exam Prep, SAP ABAP, SAP ABAP Exam, SAP ABAP Career, SAP ABAP Preparation, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development
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.

SAP ABAP Exam Prep, SAP ABAP, SAP ABAP Exam, SAP ABAP Career, SAP ABAP Preparation, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development
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)

SAP ABAP Exam Prep, SAP ABAP, SAP ABAP Exam, SAP ABAP Career, SAP ABAP Preparation, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development
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.

SAP ABAP Exam Prep, SAP ABAP, SAP ABAP Exam, SAP ABAP Career, SAP ABAP Preparation, SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Development
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.
Source: sap.com

No comments:

Post a Comment