Mendix-SAP integrations: obtaining the MYSAPSSO2 cookie

Today I’d like to share some of the hurdles we encountered while working on a SAP implementation for one of our clients. While we had the luxury of the SAP SOAP webservices being custom built for our specific application, the implementation wasn’t as straightforward as you might expect it to be. In hindsight our greatest challenge had nothing to do with the implementation of the aforementioned webservices or adhering to its (SAP-specific) structure. Instead, the part we thought would be easiest to implement (hate it when that happens 🙁 ) turned out to cause the most problems: obtaining the MYSAPSSO2 cookie (hereafter: token) for authentication of all webservice calls. More specifically, it turned out to be a matter of using HttpClient versus HTTPURLConnection.

sap_pi_ecc_mendix

MYSAPSSO2: the proof-of-concept as starting point

The case was relatively straightforward: the client’s preferred SAP supplier would build and expose several SOAP webservices for us to use in the application that we were building. Furthermore, all communication would require user-specific authorization tokens that were to be obtained from their SAP portal. This so called MYSAPSSO2 token is exposed in the response headers of a succesful HTTP GET request. There had even been a proof-of-concept project where it had already been established that these user-specific authorization tokens could be obtained by using a Java Action that contained the following snippet:

// BEGIN USER CODE
String indicator = "MYSAPSSO2=";
String header = "set-cookie";
URL url = new URL(PortalURL);
HttpURLConnection httpURLConnection = (HttpURLConnection)url.openConnection();
String userAndPass = UserName + ":" + Password;
byte[] codepoints = userAndPass.getBytes("UTF-8");
String base64 = new String(new Base64().encode(codepoints));
httpURLConnection.setRequestProperty("Authorization", "Basic " + base64);
Map<String, List<String>> map = httpURLConnection.getHeaderFields();
httpURLConnection.disconnect();
List<String> headerList = map.get(header);
for(String entry : headerList) {
	if(entry.indexOf(indicator) >= 0) {
		return entry.substring(indicator.length(), entry.indexOf(';'));
	}
}

return "";
// END USER CODE

If we take a quick look at the code, the logic is actually quite straightforward:

  1. Concatenate the username and password (separated by a colon) and apply Base64 encoding;
  2. Add a Basic authentication header to the request with Authorization as ‘key’ and Basic <Base64 encoded string> as ‘value’;
  3. Send the HTTP request to the portal;
  4. Extract the MYSAPSSO2 token from the response headers.

Putting the code to the test

Initially everything seemed to work out perfectly: we were able to successfully request a MYSAPSSO2 token, and could quickly start building added functionality for further processing and handling of the token. One of the things we had to take care of was the fact that any requested token is valid for a duration of eight hours.

Example: Adding the MYSAPSSO2 header to your webservice call (Mendix 6.5.1)
Example: Adding the MYSAPSSO2 header to your webservice call (Mendix 6.5.1)

Using the MYSAPSSO2 token as authentication of a webservice call is easy. Simply add a Custom HTTP Header to the webservice call from within the Mendix Modeler and add the following values:

  • Key = Cookie
  • Value = MYSAPSSO2=<your_token_here>

After that, you’re all set to go 🙂 !

Trouble down the road

This went on for a period of time as we were still in the early stages of building the application, which was also the case for the remaining webservices on the supplier’s side. One day we started getting warnings in the logs of our test environment. For some reason users who requested the MYSAPSSO2 token were unable to successfully extract the token. A quick analysis of the situation revealed that:

  • The username/password combination was correct;
  • The account was not blocked;
  • The connection with the token portal was not blocked;
  • The request resulted in a HTTP 200 (OK) response from the portal.

Nothing out of the ordinary, right? But why on earth was there no header to be found that contained the MYSAPSSO2 token? Further investigation of our logs (backtracing from the error occurances to when everything was working just fine) revealed that:

  • Users who previously had no problems obtaining a MYSAPSSO2 token all of a sudden were also not receiving new tokens;
  • The application would start off just fine handling all tokens, until a period of time or amount of token requests had passed.

In the end that last observation is what I couldn’t let go of. One of my colleagues figured that it had to be a connection termination problem and pointed me in the direction of closing the InputStream. Especially since we found out that httpURLConnection.disconnect(); doesn’t really remove the connection, rather it:

Indicates that other requests to the server are unlikely in the near future. Calling disconnect() should not imply that this HttpURLConnection instance can be reused for other requests.

We soon also found out that restarting the application each time the problems started occurring would result in immediately being able to request new tokens successfully again, proving once more that a problem with properly releasing the connections would very likely be the cause.

Unfortunately, even the addition of closing the InputStream using httpURLConnection.getInputStream(); didn’t seem to help as we were still facing the same problems we had earlier on.

Going from HTTPURLConnection to HttpClient

As a last ditch effort I decided now was the time to reconsider the usage of HTTPURLConnection and favour using HttpClient. My colleague had earlier expressed his preference for HttpClient to me because it provides you with more control over your connections (think of features such as connection pooling), but figured that the HTTPURLConnection method should suffice for our purposes.

After refactoring the code we started this blog post with to make use of HttpClient instead of HTTPURLConnection, I ended up with the following snippet in our Java Action:

// BEGIN USER CODE
String errorMessage = "";
String requestMessage = "";
String indicator = "MYSAPSSO2=";
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(portalURL);
String userAndPass = usernameSAP + ":" + passwordSAP;
byte[] codepoints = userAndPass.getBytes("UTF-8");
String base64 = new String(new Base64().encode(codepoints));
request.addHeader("Authorization", "Basic " + base64);
requestMessage = requestMessage + "\n" + "Authorization: Basic " + base64 + " aan header van verzoek toegevoegd";
HttpResponse response = client.execute(request);
int responseCode = response.getStatusLine().getStatusCode();
requestMessage = requestMessage + "\nHTTP (GET) response = " + responseCode;
logger.info("Nieuw tokenverzoek voor " + portalURL + requestMessage);
List<Header> httpHeaders = Arrays.asList(response.getAllHeaders());
int headerCount = httpHeaders.size();
String headerResponse = "";
try {
	InputStream is = response.getEntity().getContent();
	is.close();
	headerResponse = headerResponse + "\nhttpClient InputStream afgesloten";
} catch (Exception e)
{
	headerResponse = headerResponse + "\nMislukt: httpClient InputStream afsluiten";
}
finally
{
	request.releaseConnection();
	headerResponse = headerResponse + "\nhttpRequest connectie vrijgegeven";
}
logger.info("Response ontvangen: " + headerCount + " headers" + headerResponse);
for (Header header : httpHeaders) {
	String headerValue = header.getValue();
	errorMessage = errorMessage + "\n" + headerValue;
	if(headerValue.indexOf(indicator) >= 0) {
		String tokenValue = headerValue.substring(indicator.length(), headerValue.indexOf(';'));
		logger.info("Token succesvol ontvangen: " + tokenValue);
		return tokenValue;
	}
}		

logger.warn("Volledige response: " + errorMessage);
return "";
// END USER CODE

So.. what did we change?

You might notice some additions compared to the code we started off with. This was done (among others) in an attempt to add more logging and control over the whole process. I decided to leave in the part about closing the InputStream, as I believe that’s something we always need to take care of. But other than that, the most important bit is where we instruct that our HTTP GET request connection should be released by invoking request.releaseConnection();.

According to the Apache documentation, this should be what we’re looking for in terms of properly closing our connections to the token portal, as:

This is a crucial step to keep things flowing. We must tell HttpClient that we are done with the connection and that it can now be reused. Without doing this HttpClient will wait indefinitely for a connection to free up so that it can be reused.

Also, in Apache’s example code they commented that:

// The underlying HTTP connection is still held by the response object 
// to allow the response content to be streamed directly from the network socket. 
// In order to ensure correct deallocation of system resources 
// the user MUST either fully consume the response content  or abort request 
// execution by calling HttpGet#releaseConnection().

Finally, some good news

Now that the code had been replaced, it was time to start testing again. We received additional support from a developer on the webservices supplier’s team who was willing to help us monitor all the detailed logging on their (SAP) end.

Not long after initiating our MYSAPSSO2 token request with the refactored code, we received the confirmation we were looking for: not only did we receive a proper MYSAPSSO2 token, but they could confirm on their end that the request was being closed properly. They could see that each request contained its own session with the SAP portal, and was terminated correctly after receiving the token. The developer told us that previously a token request could result in the portal somehow incorrectly identifying the request to belong to one of the already existing sessions within the portal, causing it to completely omit the MYSAPSSO2 token in its response.

Taking the logic from Java to the Mendix Modeler

After confirming that the previously faced issues had been resolved by the new method (the result of heavy testing over a longer period on our test environment), I felt the need to have more control over the logic from within the Mendix Modeler instead of having all of it embedded in the Java code. So as a final step, I decided to alter the code in such a way that any token request would result in a list of ResponseHeaders that would be passed as output of the Java Action.

// BEGIN USER CODE
logger.debug("Initiating new token request");
HttpClient httpClient = HttpClientBuilder.create().build();
HttpGet httpRequest = new HttpGet(endpoint);
logger.debug("Generating request for endpoint " + endpoint);

// Generate the Authorization header
String authentication = username + ":" + password;
byte[] codepoints = authentication.getBytes("UTF-8");
String base64 = new String(new Base64().encode(codepoints));

// Add Authorization header to the httpRequest
httpRequest.addHeader("Authorization", "Basic " + base64);
logger.debug("\n" + "Adding header to request (Authorization: Basic " + base64 + ")");

// Execute the httpRequest
HttpResponse httpResponse = httpClient.execute(httpRequest);
tokenRequest.setResponseCode(httpResponse.getStatusLine().getStatusCode());

// Extract the headers
List<Header> httpHeaders = Arrays.asList(httpResponse.getAllHeaders());

try {
	// Close the InputStream
	InputStream is = httpResponse.getEntity().getContent();
	is.close();
	logger.debug("httpClient InputStream closed succesfully");
} catch (Exception e) {
	logger.warn("Failed to close httpClient InputStream \n" + e);
}
finally	{
	// Release the httpRequest connection
	httpRequest.releaseConnection();
	logger.debug("httpRequest connection released succesfully");
}
		
// Create the response header list
List<IMendixObject> responseHeaderList = new ArrayList<IMendixObject>();
for (Header header : httpHeaders) {
	ResponseHeader rs = new ResponseHeader(getContext());
	rs.setValue(header.getValue());
	rs.setResponseHeader_TokenRequest(tokenRequest);
	responseHeaderList.add(rs.getMendixObject());
}		
	
// Return the list of headers
return responseHeaderList;
// END USER CODE

As you can see in the above code snippet, I decided to strip the Java Action in such a way that it only performs the actions that I wouldn’t be able to do from within the Mendix Modeler. This includes setting up the HttpClient connection, executing the request, and registering the HTTP response code (200=OK).

Rather than going through the list of headers and trying to extract the MYSAPSSO2 token altogether, the Java Action now writes all headers to instances of ResponseHeader (which is a Non-Persistent Entity; NPE hereafter) and associates them with the initial TokenRequest (NPE) instance.

Example: domain model for handling the response headers
Example: domain model for handling the response headers

Put all these ingredients together and all of a sudden you go from calling a Java Action which contains all the logic (and more importantly: is not transparent for those less savvy with Java) to creating your own small module where you have full control from within the Mendix Modeler over the way the response headers are handled and processed.

The following resources were very helpful in providing examples so I could better understand how to refactor the Java Action from using HTTPURLConnection to HttpClient: Mkyong (HttpClient examples), Apache, Vogella, and Mkyong (Http response headers).

Happy coding!  😀

Leave a Reply

Your email address will not be published. Required fields are marked *