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: 
MartinB2
Explorer

Introduction


Token based authentication is prominent everywhere on the web nowadays. With most every web company using an API, tokens are the best way to handle authentication for multiple users.

In this blog I want to share a simple and generic adapter module implementation about how to obtain such a token and store it in the dynamic configuration for further use.

Additionally there's an option to cache the token for subsequent calls. This is useful if you have loads of requests and you want to save some traffic or if the number of tokens (logins) that can be obtained per hour/day is limited.

We're using this module in several REST and SOAP scenarios to request an Auth-Token that is needed in the HTTP Header of a HTTP Request. The caching function allows reusing a token within multiple XI-Messages, so the login is only done once in a specified time frame.

Implementation


In all our scenarios we had to put an Auth-Token in the HTTP Header of the request. HTTP-Header modification is possible in HTTP_AAE, REST and SOAP (AXIS) adapter. In every adapter you can access values from the Dynamic Configuration and put it into the HTTP Header. (examples are listed below)

In his very good blog post "Setting Dynamic Configuration Attributes Using Custom Adapter Module in Advanced Adapter Engine of P...", vadim.klimov is describing how to modify the dynamic configuration of an XIMessage. The provided classes to access the dynamic configuration can be extended and this is what we do here. So before you continue reading, it's a good idea to read Vadims post first. 😉

The following picture highlights all classes that have been added to the project.



 

  • DynamicConfigurationProviderHttpLookup: Implements DynamicConfigurationProvider and contains the main logic. The following extract shows the idea of what is behind:

    if (storageEnabled) {
    [...]
    parameterValue = keyValueStore.get(storageKey);
    [...]
    }

    if(parameterValue == null){
    [...]
    HttpRequest request = new HttpRequest(parameters);
    HttpResponse response = HttpClient.doRequest(request, parameters);
    parameterValue = response.getResponse(parameters);
    [...]
    if (storageEnabled) {
    [...]
    keyValueStore.add(storageKey, parameterValue,
    storageExpirationTime);
    [...]
    }
    }
    [...]
    DynamicConfigurationAttribute dcAttribute =
    new DynamicConfigurationAttribute(parameterNamespace,
    parameterName, parameterValue);
    if (dcAttribute.isDynamicConfigurationAttributeComplete()) {
    dcAttributes.add(dcAttribute);
    }
    [...]


  • KeyValueStore: Wrapper class for the MessageIDMapper class. Provides add, get and remove method. The MessageIDMapper is used to persist data in PI Tables for a specified time. If you want to have a look at the raw data, open a SQL Browser an go to table SAPJ2EE.XI_AF_SVC_ID_MAP.

  • ...util.http Package: Contains Helper Classes for HTTP-Requests.


Usage


Module Processing Sequence

  • Number: <before the module you want to use the token, usually somewhere at the beginning>

  • Module Name: Custom_AF_Modules/AddDynamicConfigurationBean

  • Type: Local Enterprise Bean

  • Module Key: <up to you> (e.g. dclookup)


Module Configuration























































































Parameter name Description
*class DynamicConfigurationProviderHttpLookup
*dc.attribute.name The name of the attribute in the Dynamic Configuration where you want to save the value received by the lookup.
*dc.attribute.namespace The namespace of the attribute in the Dynamic Configuration where you want to save the value received by the lookup.
*dc.http.request.url The URL that should be called (http://... , https://...).
dc.http.request.proxyHost If you need to route the call via a proxy, enter the Host-name of the Proxy-server here.
dc.http.request.proxyPort If you need to route the call via a proxy, enter the Port of the Proxy-server here.
dc.http.request.method HTTP Method: Only GET or POST are implemented yet. Default is POST.
dc.http.request.header.<custom>

If you need to add specific HTTP Header you can add them here. The <custom>-Tag has to be replaced by a custom name. As a value you can enter what you need, make sure to keep the following syntax:

<httpHeaderKey>: <httpHeaderValue>

e.g. If you need to add a SOAPaction you can do something like:

ParameterName: dc.http.request.header.MySoapAction

ParameterValue: SOAPAction: A_Custom_SOAP_ACTION
dc.http.request.postdata POST-Data is only available when you do a HTTP-Post. Enter the payload that you want to sent in the request.
dc.http.request.connectiontimeout HTTP connection timeout in ms, default is 60000s.
dc.http.request.readtimeout HTTP read timeout in ms, default is 60000s.
dc.http.response.valuesource It may happen, that the HTTP Response returns more information than you want to store in the dynamic configuration. This parameter accepts "Reqex" or "XPath". If set, the selected operation is applied on the HTTP-Response.
dc.http.response.valuesource.xpath If dc.http.response.valuesource=XPath, enter the Xpath of the value you want to have.
dc.http.response.valuesource.regex If dc.http.response.valuesource=Regex, enter the Regex of the value you want to have.
dc.http.response.stringformat It may also happen, that you need to format the token somehow before further use. This is just a simple String.format() operation on the http/xpath or regex result.
[String.format(<your value>, value)] As an idea, you can enter something like "Bearer %s".
dc.keyValueStore.enabled As already mentioned above, the module is able to store the received token for subsequent calls. This is enabled by setting the parameter to true. Default is false.
dc.keyValueStore.key This parameter is optional and by default initialized with the Channel-ID. It is possible to make several channels use the same storage by using the parameter. This is useful if the same token can be used in several receiver channels. E.g. If you have multiple receiver channels pointing to the same host and all can share the same token.
dc.keyValueStore.expirationTime Enter a value in minutes. Default is 1 minute.
dc.keyValueStore.clear By setting this to "true", it will disable the KeyValueStore and removes the saved token. This is useful, if you need to remove a saved token for any reason.

(* mandatory)

Http header manipulation


Once a token is received, it can be used wherever the Dynamic Configuration is accessible. The following examples show, how to add an Auth-Token in the HTTP Header of a request.

HTTP Adapter 



  • In Tab "Advanced" check "Set Adapter Specific Message Properties" and "HTTP Header Fields"

    • e.g. in Field 1 (HeaderFieldOne): AuthKey






  • In the Modul Configuration (Custom_AF_Modules/AddDynamicConfigurationBean)

    • dc.attribute.name= HeaderFieldOne (... HeaderFieldSix)

    • dc.attribute.namespace = http://sap.com/xi/XI/System/HTTP_AAE

    • ...





SOAP Adapter (AXIS Mode)



  • Have a look at the XI30DynamicConfigurationHandler for accessing the Dynamic Configuration in the SOAP Adapter.

  • The Note: "1039369 - FAQ XI Axis Adapter" shows some examples as well (search for ASMA)


REST Adapter



  • In Tab "REST URL" use Pattern Variable Replacement

    • Value Source: AdapterSpecific Key

    • Pattern Element Name: AuthKey

    • Adapter Specific Attribute: CustomAttribute

    • Attribute Name: AuthKey






  • In Tab "HTTP Headers" add a new row:

    • Header Name: e.g. Authorization

    • Value Pattern: {AuthKey}






  • In the Modul Configuration (Custom_AF_Modules/AddDynamicConfigurationBean)

    • dc.attribute.name= AuthKey

    • dc.attribute.namespace= http://sap.com/xi/XI/System/REST

    • ...





Source


You can find the sources in my fork of Vadim's Git-Repository here:

https://github.com/MartinBuselmeier/sap-xpi-adapter-module-add-dynamic-configuration

 




Et voilà! A generic module to request tokens with caching option.




 

Inheriting from DynamicConfigurationProviderHttpLookup


As you see, there are a lot of parameters to fill. Sometimes it makes sense to create a more specific implementation that fits to a special software. This makes it easier for PI Admins to maintain the Module Configuration in the channels. The following code is extracted from DynamicConfigurationProviderDemoSoftware class.

It shows how to extend the class DynamicConfigurationProviderHttpLookup and sets additional properties that are not specified by a module property by keeping the possibility to overwrite them if needed.
public class DynamicConfigurationProviderDemoSoftware extends
DynamicConfigurationProviderHttpLookup {

private static final String PARAMETER_USERNAME = "DemoSoftware.username";
private static final String PARAMETER_PASSWORD = "DemoSoftware.password";
private static final String PARAMETER_URL = "DemoSoftware.url";

@Override
public List<DynamicConfigurationAttribute> execute(Message message,
Map<String, String> parameters)
throws DynamicConfigurationProviderException {

String username = "";
String password = "";
String url = "";
for (Map.Entry<String, String> parameter : parameters.entrySet()) {
if(parameter.getKey().equals(PARAMETER_URL)) {
url = parameter.getValue();
} else if (parameter.getKey().equals(PARAMETER_USERNAME)) {
username = parameter.getValue();
} else if (parameter.getKey().equals(PARAMETER_PASSWORD)) {
password = parameter.getValue();
} else {

}
}
String postdata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soapenv:Header/>"
+ "<soapenv:Body>"
+ "<ns:Login>"
+ "<ns:userName>"
+ username
+ "</ns:userName>"
+ "<ns:password>"
+ password
+ "</ns:password>"
+ "</ns:Login>"
+ "</soapenv:Body>" + "</soapenv:Envelope>";

// Set default values if not already set by Module Parameter
if (!parameters.containsKey("http.request.url"))
parameters.put("http.request.url", url);

if (!parameters.containsKey("http.request.postdata"))
parameters.put("http.request.postdata", postdata);

if (!parameters.containsKey("http.request.header.soapAction"))
parameters.put("http.request.header.soapAction","SOAPAction: \"Login\"");
[...]
return super.execute(message, parameters);
}
}

The next picture shows the module configuration for the DynamicConfigurationProviderDemoSoftware class.



That's it 🙂

 
12 Comments
Labels in this area