Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
sudheer-tammana
Advisor
Advisor

Odata4j is a popular OData tool kit for Java. One of the reasons for using “popular” is the library's constant evolvement with every new release. So, when we set out to try the library for consuming SAP Gateway services, everything was green until CSRF (Cross-Site Request Forgery) is met.

CSRF protection is an additional validation feature enabled in SAP Gateway since Gateway 2.0/SP03 for all data modifying requests (e.g. Create, Update and Delete). This means that a valid CSRF token must first be retrieved using a non-modifying request (e.g. using the GET method). Then it can be sent along with the subsequent modifying request and validated before normal processing continues. With the helm of internet frauds, mandating such a validation is nice and welcome.

For more information about CSRF in the context of SAP Gateway, proceed here.

Now, not every library complies or provides mechanisms to handle these CSRF tokens. Odata4j isn’t an exception. Like any other library, odata4j also sets focus on the business part which is ODATA while encapsulating the actual request and response building part safely. So, there is no easy way the request and response headers can be influenced to send and receive these CSRF tokens.

Just when we gave up, this provided little solace. The documentation is outdated. With release 0.5, Jersey is used as a client. But, this gave the much needed pointer and the objective has been achieved just by making few changes and utilizing the extension points provided by odata4j.

We had to implement the interface “JerseyClientBehavior” of package (org.odata4j.jersey.consumer.behaviors) and override the methods with our own implementation handling the CSRF tokens. Below is the code-snippet explaining this further:

private static class myJerseyBehavior implements JerseyClientBehavior {

private String xsrfCookieName;
private String xsrfCookieValue;
private String xsrfTokenValue;

private String user;
private String password;

myJerseyBehavior(String user, String password) {
  this.user = user;
  this.password = password;
}
    
@Override
public ODataClientRequest transform(ODataClientRequest request) {
         String userPassword = user + ":" + password;
         String encoded = Base64.encodeBase64String(userPassword.getBytes());
         encoded = encoded.replaceAll("\r\n?", "");
        
         if (request.getMethod().equals("GET")){
         request = request.header("X-CSRF-Token", "Fetch")
        .header("Authorization", "Basic " + encoded);       
   return request;
         }else {
          request = request.header("X-CSRF-Token", this.xsrfTokenValue).header("Cookie", xsrfCookieName + "=" + xsrfCookieValue)
        .header("Authorization", "Basic " + encoded);
   return request;              
         }
}
 
@Override
public void modifyWebResourceFilters(Filterable arg0) {

}

@Override
public void modifyClientFilters(Filterable client) {
client.addFilter(new ClientFilter(){
         
@Override
public ClientResponse handle(ClientRequest clientRequest) throws ClientHandlerException {
        ClientResponse response = getNext().handle(clientRequest);
              
        List<NewCookie> cookies = response.getCookies();
              
   for (NewCookie cookie:cookies) {
    if (cookie.getName().startsWith("sap-XSRF")) {
     xsrfCookieName = cookie.getName();
     xsrfCookieValue = cookie.getValue();
     break;
    }
    
   }
  
   MultivaluedMap<String, String> responseHeaders = response.getHeaders();
   xsrfTokenValue = responseHeaders.getFirst("X-CSRF-Token");
   return response;
  }});
}
 
@Override
public void modify(ClientConfig arg0) {
}
}

A step to step description of what is done:

  • Create a client behavior as shown in the above code-snippet.

  • The transform method is overridden so that the CSRF token can be fetched for "GET" and the CSRF token can be set for any modifying requests such as "PUT", "POST" etc.

  • Create new client filter enabling to retrieve the CSRF token and the CSRF cookie values from response headers.

  • Note: Both the CSRF token and the cookie are to be set for the modifying requests to work.

  • Finally while instantiating a new jersey consumer for odata4j use the client behavior as created in the above steps. In this way, this holds good for all the requests.

After this, everything is green again

14 Comments