簡體   English   中英

HTTPS 來自 Java 的 POST 請求使用有效的 CSRF-Token 向 S/4HANA 系統中的 OData-Service 申請

[英]HTTPS POST Request from Java Application to OData-Service in S/4HANA System with valid CSRF-Token

情況如下:一方面,我創建了一個 OData 服務,它在收到 POST 請求時應該創建一個條目。 該服務在 S/4HANA 系統中創建,可通過 SAP 網關訪問。

另一方面,我有一個 Java 應用程序 (OpenJDK 11),它本質上執行一個循環,並且必須向每個循環發出一個 POST 請求到 OData 服務。

我正在使用 IntelliJ IDEA 社區版和 OpenJDK 11。這也是我第一次將 OData 與 Java 和 SAP 一起使用。

起初我嘗試了以下方法:

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());
    }

但是我得到了錯誤,我的 CSRF-Token 無效。

因此,在谷歌搜索找出什么是 CSRF-Token 之后,我嘗試首先使用自己的HttpsURLConnection創建一個 GET-Request:

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();
}

然后我會向同一個 URL 發出實際的 POST-Request,並將之前的 X-CSRF-Token 設置到 HTTPS-Header 中

con.setRequestProperty("X-CSRF-Token",theGETToken); postRequest()

但我仍然遇到同樣的錯誤。

我究竟做錯了什么?

經過更多的谷歌搜索后,我終於明白了我錯過了什么。

CSRF-Token 僅對用戶的特定session有效。 session 由 HTTPS 標頭中傳遞的 cookies 標識。

需要做的是以下內容(另請參閱: https://blogs.sap.com/2021/06/04/how-does-csrf-token-work-sap-gateway/ ):

  1. 通過發出非修改請求打開一個session,指定header獲取一個CSRF-Token和session-cookies
     HTTP-Request: Type: GET Header-Fields: x-csrf-token = fetch set-cookie = fetch
  2. 為 POST 請求保存 CSRF-Token 和 session-cookies
  3. 發出 POST 請求並根據保存的值設置會話 cookie 和 CSRF 令牌
    HTTP-Request: Type: POST Header-Fields: x-csrf-token = <tokenFromGet> cookie = <allSessionCookies>

請注意,請求的 header 字段名為cookie而不是set-cookie ,並將 set- set-cookie的 HeaderField 的所有值傳遞給 POST-Request-Header。

值得一提的是,CSRF 令牌和會話 cookie 在提供或調整的時間范圍或對 session 進行任何更改后過期,並且必須重新獲取兩者(請參閱https://blogs.sap。 com/2021/06/04/how-does-csrf-token-work-sap-gateway/#comment-575524 )。

我的工作代碼示例:

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();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM