API Token via Http Lookup in Adapter Module
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 PI/PO“, 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:
- Module Name: Custom_AF_Modules/AddDynamicConfigurationBean
- Type: Local Enterprise Bean
- Module Key:
(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 (https://… , 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. |
If you need to add specific HTTP Header you can add them here. The 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( |
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 = https://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= https://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 execute(Message message, Map parameters) throws DynamicConfigurationProviderException { String username = ""; String password = ""; String url = ""; for (Map.Entry 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 = "" + "" + " " + "" + "" + "" + username + " " + "" + password + " " + " " + "" + " "; // 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 ????
New NetWeaver Information at SAP.com
Very Helpfull