Delphi - Komponente - TNetHTTPClient und Sonderzeichen
Posted by wnf on Wednesday, 28 April 2021Beim Zugriff auf die Keycloak-API zum Abruf eines Tokens kommt die Fehlermeldung 401 Not Authorized wenn das Passwort ein Sonderzeichen enthält.
In einer Python-Testumgebung kann der Token ohne Probleme abgerufen werden.
Mit Wireshark kann man sich die erzeugten Abfragen anzeigen (damit man in Wireshark die unverschlüsselten Daten angezeigt bekommt muss man zum Debuggen die URL von https:// auf http:// umstellen):
Python erzeugt:
POST /keycloak/rest/v1/token/generate HTTP/1.1 Host: demo.wlsoft.de User-Agent: python-requests/2.22.0 Accept-Encoding: gzip, deflate Accept: */* Connection: keep-alive Content-Type: application/x-www-form-urlencoded Authorization: Basic xxxx:yyyyyyyyyyyyyyyy Content-Length: 70 grant_type=password&username=xxxxx@xxxxxx.xx&password=123456789%C3%840
Delphi erzeugt
POST /keycloak/rest/v1/token/generate HTTP/1.1 Host: demo.wlsoft.de User-Agent: Embarcadero URI Client/1.0 Accept-Encoding: gzip, deflate Accept: */* Connection: keep-alive Content-Type: application/x-www-form-urlencoded; charset=utf-8 Authorization: Basic xxxx:yyyyyyyyyyyyyyyy Content-Length: 70 grant_type=password&username=xxxxx@xxxxxx.xx&password=123456789%C3%840
Der Unterschied steckt im Content-Type
Python: Content-Type: application/x-www-form-urlencoded; Delphi: Content-Type: application/x-www-form-urlencoded; charset=utf-8
Der falsche Eintrag in Delphi wird durch die Funktion THTTPClient.CreateFormFromStrings() erzeugt:
Deshalb darf bei Abruf eines Token nur eine Post-Anweisung aufgerufen werden, die CreateFormFromStrings() nicht aufruft.
Das wäre zum Beispiel ein solcher Aufruf:
function TNetHTTPClient.Post( const AURL: string; const ASource, AResponseContent: TStream; const AHeaders: TNetHeaders): IHTTPResponse;
type THCloudZugangsdaten = record User: string; Pass: string; Token:string; TokenResponseCode: integer; TokenResponseText: string; end; function getToken(var aZugangsdaten: TZugangsdaten): boolean; var s, aURL: string; j: TJSONObject; aSource: TStringStream; m: TMemoryStream; sl: TStringlist; Response: IHTTPResponse; HTTP: TNetHTTPClient; aHeaders: TNetHeaders; begin Result := False; sl := TStringlist.Create; aSource := TStringStream.Create( 'grant_type=password' + '&username=' + EncodeURIComponent(aZugangsdaten.User) + '&password=' + EncodeURIComponent(aZugangsdaten.Pass)); m := TMemoryStream.Create; HTTP := TNetHTTPClient.Create(nil); try try HTTP.ContentType := cContentType_x_www_form_urlencoded; HTTP.AcceptEncoding := 'gzip, deflate'; HTTP.CustomHeaders['Accept'] := '*/*'; HTTP.CustomHeaders['Authorization'] := 'Basic xxxx:yyyyyyyyyyyyyyyy'; aURL := 'https://demo.wlsoft.de/keycloak/rest/v1/token/generate'; Response := HTTP.Post(aURL, aSource, m, aHeaders); if Response.StatusCode = 200 then begin m.Position := 0; sl.LoadFromStream(m); s := sl.Text; j := TJSONObject.ParseJSONValue(s) as TJSONObject; try if Assigned(j) then begin if j.TryGetValue('access_token', aZugangsdaten.Token) then begin end; end; finally FreeAndNil(j); end; end; aZugangsdaten.TokenResponseCode := Response.StatusCode; aZugangsdaten.TokenResponseText := Response.StatusText; Result := not aZugangsdaten.Token.IsEmpty; except on E: Exception do begin aZugangsdaten.TokenResponseCode := Response.StatusCode; aZugangsdaten.TokenResponseText := E.Message; // kein raise; end; end; finally m.Free; aSource.Free; FreeAndNil(sl); FreeNetHttp(HTTP); end; end;