[gelöst] HttpClient Anbindung für BZSt DAC7 / Fiddler in NAV

22. Januar 2024 19:11

Moin,

wegen der neuen Plattformrichtline darf ich aktuell die API anbinden bzw. dann auch die Daten entsprechend aufbereiten.
Nachdem ich es endlich geschafft habe ein Token zu generieren (eine Qual mit der Signierung), scheitere ich nun direkt am nächsten Punkt.
Folgende Dokumentation gibt es https://www.bzst.de/DE/Service/Portalin ... _node.html

Dort ist dann auch das Vorgehen grob beschrieben. Auf jeden Fall zum Problem. Ich erhalte das Token zurück und speichere es mir ab.
Falls ich nun per API-Test-Tool oder Fiddler die Abfrage an mds-ktst.bzst.bund.de/dip/start/DAC7 mache, funktioniert es mit dem Token einwandfrei.
Raw Request von Fiddler z. B.
Code:
POST https://mds-ktst.bzst.bund.de/dip/start/DAC7 HTTP/2
Host:mds-ktst.bzst.bund.de
Authorization:Bearer xxxxxx


Wenn ich das gleiche aber in NAV mit dem HttpClient versuche, erhalte ich eine 401 Fehlermeldung:
Code:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>401 Unauthorized</title>
</head><body>
<h1>Unauthorized</h1>
<p>This server could not verify that you
are authorized to access the document
requested.  Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.</p>
</body></html>


Als Header gebe ich nur das Token an wie auch im ersten Beispiel zu Fiddler. Testweise habe ich auch mal Host hinzugefügt. HttpClient scheint nicht so einfach ein RAW rauszurücken.
Code ist zusammengedampft aus den Funktionen ca. so

Code:
HttpClientBZSt := HttpClientBZSt.HttpClient;
HttpClientBZSt.BaseAddress := UriBZSt.Uri('https://mds-ktst.bzst.bund.de');
HttpClientBZSt.DefaultRequestHeaders.Clear;
HttpClientBZSt.DefaultRequestHeaders.Add('Host', 'mds-ktst.bzst.bund.de');
HttpClientBZSt.DefaultRequestHeaders.TryAddWithoutValidation('Authorization', STRSUBSTNO('%1 %2', 'Bearer', GlobalTokenBzst)); // Variable ist auch gefüllt mit dem richtigen Token, getestet per MESSAGE(FORMAT(HttpClientBZSt.DefaultRequestHeaders));
HttpResponseMessage := HttpClientBZSt.PostAsync('/dip/start/DAC7', HttpContentPar).Result; // HttpContentPar ist dabei eine leere Variable, weil es keinen Content gibt
ResultStringPar := HttpResponseMessage.Content.ReadAsStringAsync.Result; // MESSAGE darauf ergibt dann obige Fehlermeldung


Vielleicht hat jemand eine Idee?

#EDIT
Ich habe nach blöden Problemen mit Fiddler "The remote certificate is invalid according to the validation procedure" endlich den Request geloggt bekommen. Dazu habe ich auf dem NST https://bizzbrainblog.wordpress.com/201 ... -with-nav/ befolgt, dann den Port in Fiddler geändert und im Code noch folgendes benötigt (um besagten Fehler loszuwerden für Tests - natürlich niemals für produktiv):
Code:
HttpClientHandlerBzst := HttpClientHandlerBzst.HttpClientHandler;
HttpClientHandlerBzst.ServerCertificateCustomValidationCallback := HttpClientHandlerBzst.DangerousAcceptAnyServerCertificateValidator;
HttpClientBZSt := HttpClientBZSt.HttpClient(HttpClientHandlerBzst);


Dann habe ich jetzt entdeckt, dass scheinbar gar kein Bearer mitgegeben wird?? Ich verstehe nicht warum, weil oben die Message wirft bei den Headern genau diesen raus. Es muss also beim Senden einfach abgeschnitten werden o. ä.
Demnach ist die Response folgende:
Code:
Bearer error="invalid_request", error_description="No bearer token found in the request"


Also muss ich irgenwie herausfinden wieso das Weggeworfen wird und das verhinden. Auch gerne Tipps, wenn es jemandem nicht unbekannt scheint, während ich weiter forsche.

#EDIT2
Letzteres hat mich nochmal meine Existenz hinterfragen lassen. Nachdem klar war, dass der Header weg war, konnte ich die Suche stark eingrenzen. Der Beispielcode hätte auch nicht geholfen, da der Fehler in einem nicht vorgehaltenen Teil war. Ich frage nämlich direkt vom Request ab, ob der Client initialisiert ist mittels globalem Bool, das ich beim Initialisieren setze. Ich habe aber Copy&Paste gemacht aus vorherigen Clients von uns und dabei versehentlich ein isInitializedAndererClient genommen und auf TRUE gesetzt. Demnach war das isInitializedBZSt immer auf FALSE und der hat mir jedes Mal einen neuen Client erstellt direkt vor dem Request abfeuern. Wieso das beim Token widerum funktioniert hinterfrage ich erst gar nicht.
Damit ist das Thema gelöst, hat mich auch nur 1.5 Tage Nerven gekostet :)

Re: [gelöst] HttpClient Anbindung für BZSt DAC7 / Fiddler in

13. Februar 2024 16:31

Hey,

Wir sind auch gerade dabei die Richtlinien für das PSttG umzusetzen und scheitern schon an der Anmeldung. Wir haben ein Zertifikat wie in dem Beispiel angeben generiert und bei der Registrierung der Schnittstelle hinterlegt.
Wenn wir jedoch das Zertifikat versuchen zu laden, kriegen wir nur Fehler um die Ohren geschmissen. Erst als wir unser RSA_PSS Zertifikat in ein normales RSA umgewandelt haben, konnten wir es laden.
Wenn wir jetzt damit Anfragen an die Schnittstelle schicken, bekommen wir aber nur 400 - Bad Request.

Wir benutzen kein MSDynamic, sondern c#, aber vielleicht kannst du mir trotzdem hilfreiche Infos geben?

Re: [gelöst] HttpClient Anbindung für BZSt DAC7 / Fiddler in

18. Juli 2024 15:08

apimapi hat geschrieben:Ich erhalte das Token zurück und speichere es mir ab.


Hallo, ich versuche aktuell auch diese Anbindung hin zu bekommen. Leider scheitert es bei mir schon an der Anfrage einen neuen Access Token zu bekommen.
Könntest Du mir evtl. zeigen, mit welchen Werten Du den Token generieren lässt?

Dr31zehn hat geschrieben: Wir haben ein Zertifikat wie in dem Beispiel angeben generiert und bei der Registrierung der Schnittstelle hinterlegt.
Wenn wir jedoch das Zertifikat versuchen zu laden, kriegen wir nur Fehler um die Ohren geschmissen. Erst als wir unser RSA_PSS Zertifikat in ein normales RSA umgewandelt haben, konnten wir es laden.
Wenn wir jetzt damit Anfragen an die Schnittstelle schicken, bekommen wir aber nur 400 - Bad Request.


Hallo, konntest Du das Problem mit dem Token lösen und mir evtl. ein Beispiel vom Aufbau des JWT zeigen?




Hier ist mein Aufbau des JWT:
Code:
       public function createJWT() {
        $key = file_get_contents(self::DIP_PATH_TO_PRIVATE_KEY);
        $jwt = new JWT($key, 'HS256');
        $token = $jwt->encode([
            'iss' => self::DIP_CLIENT_ID, // die ID, die ich bei der Registrierung bekommen habe
            'sub' => self::DIP_CLIENT_ID, // die ID, die ich bei der Registrierung bekommen habe
           'aud' => self::DIP_URL . 'auth/realms/mds', // DIP_URL: https://mds-ktst.bzst.bund.de/
            'iat' => Carbon::now()->timestamp,
            'exp' => Carbon::now()->addMinutes(5)->timestamp,
            'jti' => str_random(35),
            'nbf' => Carbon::now()->subMinute()->timestamp
        ]);
 
        return $token;
    }

Die Fehlermeldung die ich bekomme bedeutet laut der Doku, dass ein Wert wie Audience etc. entweder falsch ist oder es ein Problem mit meinem SSL gibt.
Evtl. könnte ich an euren Beispielen sehen, was bei mir falsch ist.

Vielen Dank
Vorlon.Tech

Re: [gelöst] HttpClient Anbindung für BZSt DAC7 / Fiddler in

16. Oktober 2024 16:53

VorlonTech hat geschrieben:Evtl. könnte ich an euren Beispielen sehen, was bei mir falsch ist.


Dr31zehn hat geschrieben:Wir benutzen kein MSDynamic, sondern c#, aber vielleicht kannst du mir trotzdem hilfreiche Infos geben?


Hey, eventuell zu spät, aber hier mal wie es bei uns läuft.

Header erstellen: DefaultRequestHeaders.Add('ContentType', ReturnApplicationFormURLEncoded);
Dann Token generieren:
Beispieltoken Ausgangspunkt:
Code:
Header
  "alg": "RS256",
  "typ": "JWT"
Payload
  "iss": "CLIENT-ID vom BZST vergeben nach Beantragung im Portal",
  "sub": "CLIENT-ID",
  "aud": "https://mds-ktst.bzst.bund.de/auth/realms/mds",
  "iat": "unixtime",
  "exp": "unixtime+5min",
  "jti": "{generierteguid}",
  "nbf": "unixtime-2h1m"


Anschließend lass ich dann beides einzeln zu BASE64 konvertieren:
Base64Convert.ToBase64String(Encoding.UTF8.GetBytes(JSonHeader)); (gleiche für Payload)
Dann für beide + durch -, / durch _ ersetzen und Leerzeichen entfernen
Anschließend vereine ich das Ergebnis als Header + '.' + Payload.
Diesen String signiere ich dann mit dem generierten Private Key aus der Anleitung.
Hier erkläre ich mal nicht (Keytext = der Private Key als String inklusive der ---- Zeilen)

Code:
SignArray := EncodingUtf8.UTF8.GetBytes(SignStringPar);
StringReader := StringReader.StringReader(KeyText);
PemReader := PemReader.PemReader(StringReader);
AsymetricKeyParameter := PemReader.ReadObject;
ISigner := SignerUtilities.GetSigner('SHA256WithRSA');
ISigner.Init(TRUE, AsymetricKeyParameter);
ISigner.BlockUpdate(SignArray, 0, SignArray.Length);
SignatureArray := ISigner.GenerateSignature;
EXIT(Base64Convert.ToBase64String(SignatureArray));


Das Ergebnis jage ich auch wieder durch das Zeichen ersetzen wie oben.
Dann verbinde ich das Vorher mit dem Nachher, also das "H.P" mit der Signatur, quasi "H.P.S"
Das landet dann im Stringcontent bzw. Httpcontent als RequestToken:

Code:
StringContent := StringContent.StringContent('grant_type=client_credentials&scope=openid&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=' + RequestToken, Encoding.UTF8, ReturnApplicationFormURLEncoded);
HttpContent := StringContent;


Das Poste ich dann gegen '/auth/realms/mds/protocol/openid-connect/token' und erhalte JSON zurück, wo ich mir dann das "access_token" hole, welches der Bearer ist für alle weiteren Requests.