The situation is as follows: On the one side I have created an OData-Service which should create an entry when it receives a POST-Request. The Service is created in an S/4HANA System and is reachable via the SAP-Gateway.
On the other hand I have a Java Application (OpenJDK 11) which does essentially a loop and must issue every loop a POST-Request to the OData-Service.
I'm using IntelliJ IDEA Community Edition and OpenJDK 11. Also this is my first time using OData both with Java and SAP.
At first I tried the following:
private static void postRequest() throws IOException {
//Setting authenticator needed for login
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password.toCharArray());
}
};
Authenticator.setDefault(authenticator);
//Creating the connection
URL url = new URL("<my_service_link>");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json; utf-8");
con.setRequestProperty("Accept", "application/json");
con.setDoOutput(true);
try(OutputStream os = con.getOutputStream()) {
byte[] input = this.getJsonRequest().getBytes("utf-8");
os.write(input, 0, input.length);
}
//Reading response
int status = con.getResponseCode();
Reader streamReader = null;
if (status > 299) {
streamReader = new InputStreamReader(con.getErrorStream());
} else {
streamReader = new InputStreamReader(con.getInputStream());
}
BufferedReader in = new BufferedReader(streamReader);
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
}
But I got the error, that my CSRF-Token is invalid.
So after googling to find out what an CSRF-Token is I tried to create a GET-Request first with its own HttpsURLConnection
:
private static String getRequest() {
//Setting authenticator needed for login
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password.toCharArray());
}
};
Authenticator.setDefault(authenticator);
//Creating the connection
URL url = new URL("<my_service_link>");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json; utf-8");
con.setRequestProperty("X-CSRF-Token","fetch");
con.connect();
return con.getHeaderField("x-csrf-token").toString();
}
Then I would issue the actual POST-Request to the same URL and set the previous X-CSRF-Token into the HTTPS-Header with
con.setRequestProperty("X-CSRF-Token",theGETToken);
in postRequest()
But I still got the same error.
What am I doing wrong?
After some more googling I eventually understood what I was missing.
The CSRF-Token is only valid for a specific session of a user. The session is identified by the cookies passed in the HTTPS-Header.
What needs to be done is the following (also see: https://blogs.sap.com/2021/06/04/how-does-csrf-token-work-sap-gateway/):
HTTP-Request:
Type: GET
Header-Fields: x-csrf-token = fetch
set-cookie = fetch
HTTP-Request:
Type: POST
Header-Fields: x-csrf-token = <tokenFromGet>
cookie = <allSessionCookies>
Beware that the header field of a request is named cookie
instead of set-cookie
and to pass all values of the HeaderField of set-cookie
to the POST-Request-Header.
It is also important to mention, that the CSRF-Token as well as the session-cookies expire after a provided or adjusted timeframe or any changes to the session are made and both must be fetched anew (see https://blogs.sap.com/2021/06/04/how-does-csrf-token-work-sap-gateway/#comment-575524).
Example of my Working Code:
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
public class ODataLogger {
private String sessionCookies;
private String csrfToken;
public ODataLogger() {}
public void logOdata (String user, String pass, String jsonBody) throws IOException {
this.setDefaultAuthenticator(user, pass);
fetchSessionHeaderFields();
postRequest(jsonBody);
}
private void setDefaultAuthenticator (String user, String pass) {
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, pass.toCharArray());
}
};
Authenticator.setDefault(authenticator);
}
private void fetchSessionHeaderFields() throws IOException {
URL url = new URL("<my-service-link>");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("x-csrf-token", "fetch");
con.setRequestProperty("set-cookie","fetch");
//Reading Response
int status = con.getResponseCode();
Reader streamReader = null;
if (status < 299) {
StringBuffer sb = new StringBuffer(con.getHeaderFields().get("set-cookie").toString());
//Delete leading [ and trailing ] character
sb.deleteCharAt(this.sessionCookies.length()-1);
sb.deleteCharAt(0);
this.sessionCookies = sb.toString();
this.csrfToken = con.getHeaderField("x-csrf-token");
return;
}
}
private void postRequest(String jsonBody) throws IOException {
//Creating the connection
URL url = new URL("<my-service-link>");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("x-csrf-token", this.csrfToken);
con.setRequestProperty("Cookie", this.sessionCookies);
con.setRequestProperty("Accept", "application/json");
//Setting JSON Body
con.setDoOutput(true);
try(OutputStream os = con.getOutputStream()) {
byte[] input = jsonBody.getBytes("utf-8");
os.write(input, 0, input.length);
}
//Reading response
int status = con.getResponseCode();
Reader streamReader = null;
if (status > 299) {
streamReader = new InputStreamReader(con.getErrorStream());
} else {
streamReader = new InputStreamReader(con.getInputStream());
}
BufferedReader in = new BufferedReader(streamReader);
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();