Einzelnen Beitrag anzeigen

Bbommel

Registriert seit: 27. Jun 2007
Ort: Köln
659 Beiträge
 
Delphi 12 Athens
 
#5

AW: Probleme mit SOAP von 10.2.3 nach 10.3.1

  Alt 12. Sep 2019, 12:40
Vorweg die Bitte an einen Moderator: kann einer von euch Mods das Thema von "REST" nach "SOAP" umbenennen, damit es auch von anderen korrekt gefunden wird? Ich hatte damals beim Erstellen offenbar einen kleinen Aussetzer.

Ich habe die Lösung für das Problem. Leider ist es auch in Delphi Rio 10.3.2 noch immer ein Bug in der Unit System.Net.HttpClient.Win, den man nur gelöst bekommt, indem man eine eigene Version der Unit in sein Projekt einbaut und die entscheidende Stelle patcht. Wer bessere Lösungen hat, gerne melden.

Ich versuche nochmal, das Problem, den Fehler und eine mögliche Lösung kurz zusammenzufassen (dann braucht ihr die bisherigen Beiträge nicht unbedingt lesen):

Problem:
Es soll ein Web Service innerhalb eines Unternehmsnetzwerks genutzt werden. Der Web Service wird auf einem Windows Server gehostet (konkret geht es hier um Web Services von Microsoft Dynamics NAV) und verlangt eine Authentifizierung, damit er genutzt werden kann. Da das ganze innerhalb eines Unternehmensnetzwerk passiert, kann eigentlich NTLM/Kerberos für die Authentifizierung genutzt und dabei die Credentials das aktuellen Benutzers genutzt werden. Heißt vereinfacht: Windows kümmert sich im Hntergrund um die Anmeldung am Web Service und der Benutzer muss seine Login-Daten nicht erneut eingeben bzw. man muss sie nicht selber irgendwo speichern. Bis Delphi 10.2.3 funktionierte das meist problemlos, ab Delphi 10.3.1 hat Emba die ganzen SOAP-Bibliotheken technisch komplett umgebaut, um sie auf THTTPClient umzubauen. Damit wird unter Windows nun nicht mehr die API WinInet, sondern WinHttp benutzt.

Der Fehler:
Wenn WinHttp mit NTLM arbeitet, dann gibt es eine Option, welche steuert, ob es mit den aktuellen Benutzer-Credentials arbeiten soll oder ob man Benutzername/Passwort angeben muss. Diese Option heißt WINHTTP_OPTION_AUTOLOGON_POLICY. Weist man ihr den Wert WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW zu, dann benutzt die Bibliothek den aktuellen Benutzer für den Login und Benutzername/Passwort werden erst gar nicht abgefragt. Weist man der Option hingegen den Wert WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH zu, dann braucht man auf jeden Fall Benutzername/Passwort. In Rio 10.3.1 wurde das überhaupt nicht beachtet, somit war ein Login zum Web Service unter Benutzung des aktuellen Benutzers nicht möglich. In Rio 10.3.2 hat man versucht, das Problem anzugehen und die neue Eigenschaft "useDefaultCredentials" im THTTPClient und im WebNode eingeführt. Dann gibt es folgende Funktion:

Delphi-Quellcode:
procedure TWinHTTPRequest.SetWinLogonPolicy;
var
  LClient: TWinHTTPClient;
  LOption: DWORD;
begin
  LClient := TWinHTTPClient(FClient);
  if LClient.UseDefaultCredentials then
    LOption := WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW
  else
    LOption := WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH;
  WinHttpSetOption(FWRequest, WINHTTP_OPTION_AUTOLOGON_POLICY, @LOption, SizeOf(LOption));
end;
Sieht eigentlich super aus, funktioniert aber nicht. Das Problem ist, dass die das "UseDefaultCredentials" nicht einfach im THTTPClient speichern, sondern in einem Record, der wiederum irgendwie in einem Dictionary steckt - da war ich dann irgendwann mit meinem Latein oder meiner Geduld am Ende und es mag sich gerne noch mal jemand ansehen. Der Effekt ist jedenfalls der: man weist zwar in seinem Code dem UseDefaultCredentials ein "true" zu, aber wenn dann die Verbindung tatsächlich initialisiert und somit die obige Funktion aufgerufen wird, dann ist es trotzdem immer noch "false". Die Folge: die Option WINHTTP_OPTION_AUTOLOGON_POLICY wird auf den falschen Wert gesetzt und WinHttp erwartet einen Aufruf von "SetCredentials" mit manuell mitgegebenem Benutzernamen und Passwort.

(hässlicher) Workaround:
Aktuell fällt mir nichts besseres ein, als eine eigene Version der Unit in mein Projekt zu kopieren und dort die Funktion wie folgt anzupassen:

Delphi-Quellcode:
procedure TWinHTTPRequest.SetWinLogonPolicy;
var
  LClient: TWinHTTPClient;
  LOption: DWORD;
begin
  LClient := TWinHTTPClient(FClient);
  if LClient.UseDefaultCredentials or forceSecurityLevelLow then
    LOption := WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW
  else
    LOption := WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH;
  WriteDebugMessage('SetWinLogonPolicy: call WinHttpSetOption with Option '+IntToStr(LOption));
  WinHttpSetOption(FWRequest, WINHTTP_OPTION_AUTOLOGON_POLICY, @LOption, SizeOf(LOption));
end;
Der entscheidende Unterschied ist nur if LClient.UseDefaultCredentials or forceSecurityLevelLow then . Ich habe hier im interface-Teil noch eine globale Variable ergänzt und kann diese nun vom eigentlichen Programm aus auf true setzen, um das zu erreichen, was ja eigentlich "UseDefaultCredentials" selber tun sollte. Dann klappt ein Login am Server, ohne dass man Benutzername/Passwort irgendwo speichern und angeben muss.

Ich werde dann wohl mal einen Eintrag bei QC dazu machen...
  Mit Zitat antworten Zitat