Seite 1 von 1

QNetworkAccessManager - Authentication bei HTTP-POST

Verfasst: 15. Januar 2009 05:06
von FaS
Sorry vielleicht etwas lang, aber ich habe viele Versuche protokolliert, um das (scheinbare) Fehlverhalten genauer darzulegen.

Ich verwende QNetworkAccessManager und versuche mich gegenüber einer PHP-Seite zu authentifizieren.

Mit HTTP-GET funktioniert alles. Aber mit POST gibt es Probleme:
Ich sende eine Datei mit HTTP-POST, er lädt sie auf den Server, und der Slot authenticationRequired wird aufgerufen. Tue ich dort nichts, bekomme ich "Abbrechen gedrückt" als reply vom PHP-Script in QNetworkManager::finished(...). Das ist normales Verhalten.

Setze ich jedoch Benutzername und Passwort im QAuthenticator-Objekt, passiert nichts. Egal, was ich dort eintrage. Kein weiteres Signal wird aufgerufen (nicht im QNetworkAccessManager und nicht im QNetworkReply, welches von QNetworkAccessManager::post(...) zurückgegeben wurde.
Das PHP-Script reagiert jedoch, wie ich anhand seiner Log-Datei sehen konnte: Sind die Zugangsdaten korrekt, fährt es fort, sind sie falsch, sendet er die 401-Header erneut. Qt scheint auf nichts zu reagieren. Sind die Zugangsdaten richtig und fährt das Script fort (bzw. wird es durch wen auch immer erneut aufgerufen), sind die $_POST/$_FILES-Variablen bei diesem Durchlauf jedoch nicht mehr gefüllt. Wieso? Über ein HTML-Formular mit Firefox funktioniert das, selbst wenn man mehrmals falsche Zugangsdaten eingibt. Firefox scheint jedoch die Datei nach jeder Zugangsdateneingabe erneut hochzuladen.

Also dachte mir, vielleicht muss ich den request erneut senden. Im authenticationRequired-Signal habe ich dann "QNetworkReply *newReply = manager->post( reply->request(), *m_bytes );" hinzugefügt, nachdem ich die Zugangsdaten gesetzt habe. Ergebnis: Das PHP-Script wird 2x aufgerufen und sendet die 401-Header, anschließend wird es 2x aufgerufen mit erfolgreicher Authentifizierung aber ohne $_POST/$_FILES-Daten. Qt hat aber nur 1x authenticationRequired() aufgerufen, und für meinen 2. Post-Befehl lediglich QNetworkReply::uploadProgress(...) beim newReply. Wegen dem einen authenticationRequired() aber den zwei erfolgreichen Authentifizierungen, nehme ich an, dass QNetworkAccessManager intern noch irgendwie funktioniert. Aber ich bekomme keine Signale. Und die leeren POST-Variablen sind auch dumm.
Setze ich falsche Zugangsdaten, sendet das PHP-Script zum Schluss die Header neu, also insgesamt 4x, bei den letzten 2 ohne Post-Daten. Und Qt ruft beim 2. Mal auch kein authenticationRequired() mehr auf.

Ich weiß einfach nicht weiter.

Ein "reply->abort();" im authenticationRequired() lässt den manager und das reply ein finished() senden, aber den manager kann ich danach trotzdem nicht weiter verwenden, um z.B. den request mit seinen nun gespeicherten Zugangsdaten zu wiederholen (das reply sendet uploadProgress(...), das wars dann wieder, der manager sagt garnichts mehr).

Ich hoffe, ich mache etwas grundlegend falsch, und jemand weiß, was... Dass erst die Dateien hochgeladen werden, und danach erst authentifizert werden kann, das kanns ja sowieso auch nicht sein.. aber es funktioniert halt mit dem HTML-Formular, und notfalls ist das auch nicht tragisch..

Beginn des PHP-Scripts (das Verarbeiten der POST-Anfrage habe ich weggelassen):
<?php

$file = $_POST['userdir'].$_FILES['userfile']['name'];
$handle = fopen( "log.txt", 'a' );
fwrite( $handle, "file: $file \n" );

if( !isset( $_SERVER['PHP_AUTH_USER'] ) || $_SERVER['PHP_AUTH_USER'] != "user" || $_SERVER['PHP_AUTH_PW'] != "pass" )
{
// Log
fwrite( $handle, "sending authentication request\n" );
fclose( $handle );

header( 'WWW-Authenticate: Basic realm="Zugangsdaten"' );
header( 'HTTP/1.0 401 Unauthorized' );

echo 'Abbrechen gedrückt';
exit;
}

// Log
fwrite( $handle, "authentication successful\n" );
fclose( $handle );

echo 'Erfolgreich authentifiziert.';
Die Dokumentation sagt nichts weiter dazu, außer, dass man sie so verstehen könnte, dass die Zugangsdaten erst beim nächsten request verwendet werden. Aber wenn das so wäre, würde sich bestimmt nicht dieses "andere" Verhalten zeigen, wenn man denn die Daten setzt. Und beim nächsten Request reagiert der manager ja sowieso nicht mehr:
void QNetworkAccessManager::authenticationRequired ( QNetworkReply * reply, QAuthenticator * authenticator ) [signal]

This signal is emitted whenever a final server requests authentication before it delivers the requested contents. The slot connected to this signal should fill the credentials for the contents (which can be determined by inspecting the reply object) in the authenticator object.

QNetworkAccessManager will cache the credentials internally and will send the same values if the server requires authentication again, without emitting the authenticationRequired() signal. If it rejects the credentials, this signal will be emitted again.
Ich bitte um Hilfe,
FaS

Verfasst: 18. Januar 2009 03:45
von FaS
Ich habe das Problem nun über einige Umwege, inkl. der Benutzung von Sessions, gelöst. Gefällt mir insgesamt nicht so gut, aber es funktioniert wenigstens:

Falls es jemand interessiert, kann ich den Code irgendwie verfügbar machen.

Anforderungen:
- HTTP-Server, Cookies, PHP-Sessions
- QNetworkAccessManager

Features (ohne Gewähr):
- Kein HTTPS für sichere Authentifizierung nötig.
- Digest Access Authentication (Passwort wird nicht reproduzierbar übertragen).
- Authentifikation kann nach einem Logout nicht wiedererlangt werden, egal was im Netzwerk abgefangen wurde, außer, man kennt das Passwort.
- Aber selbst während man eingeloggt ist, kann man keine vergangenen Requests wiederholen, da im Hash ein Zähler eingearbeitet ist, welcher immer größer sein muss, als der des letzten requests (die Erhöhung des Zählers gehört zum Digest Access Authentication-Verfahren (Qt bzw. Browser machen das automatisch), die Überprüfung, ob er größer ist, wird ebenfalls durch eine Session-Variable gelöst).

Benutzung und Funktionsweise:
- Die zu schützende PHP-Seite (welche in meinem Fall von Qt als POST-Ziel verwendet wird, Beispiel: "upload.php") muss zu Beginn "authenticate_digest.php" includieren.
- Der Rest der Datei (nach der Includierung) wird nur ausgeführt, wenn die Authentifizierung erfolgreich ist.
- authenticate_digest.php muss nicht geschützt werden.
- Schritt 1: GET /upload.php?login
Dummy-Seite. Eine Session-ID wird in einem Cookie zurückgegeben. Bei Firefox und IE kann das Cookie auch während der Authenticate-Anfrage erstmalig angelegt werden (Schritt 2), bei Chrome und Qt nicht, sodass diese Clients zunächst diese Dummy-Seite aufgerufen müssen.
- Schritt 2: GET /upload.php?auth
Eine Authenticate-Anfrage wird mit der gespeicherten Digest-Authentifizierungs-ID, welche zu der aktuellen Session gehört (identifizert durch das Cookie) gestartet, falls man nicht authentifiziert ist. Mit Hilfe der Session-Daten werden nur 2 Versuche gestattet, danach muss man sich ausloggen. Dadurch wird eine Endlosauthentifizierung verhindert, falls das Programm ständig falsche Anmeldedaten übergibt (das ist keine Sicherheitsmaßnahme, sondern ein Serverschutz). Der erste Versuch ist für den internen Credentials-Cache, der zweite für die Neueingabe, falls Qt mit dem Cache erfolglos war.
- Schritt 3: Gewünschte Anfragen mit upload.php durchführen
- Schritt 4: GET /upload.php?logout
Session und Cookie wird gelöscht. Durch das Löschen der Session wird die bisherige Digest-Authentifizierungs-ID ungültig gemacht.

Ein Problem könnte auftreten, wenn die Session serverseitig gelöscht wird (z.B. durch ein Timeout, wenn der Upload sehr lange dauert). Das würde die Datei verwerfen, da die PHP-Seite erst nach dem Übertragen der POST-Daten aufgerufen wird, und dann zunächst die Authentifizierung startet. Möglicherweise kann das Problem gelöst werden, indem während des Uploads in einem bestimmten Zeitintervall ein GET /upload.php?auth abgesetzt wird.