HTTP Basic Authorization mit wechselndem Realm

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Möchte via Python eine Website besuchen und Daten extrahieren. Die Website ist geschützt mit HTTP Basic Authorization.

Für die Autorisierung braucht man einen sog. Realm. Da ich bis heute nicht so richtig weiß was das ist, habe ich den HTTP Request einfach mit try/except versucht, den Fehler abgefangen und über den zurück gelieferten "www-authenticate" Header den Realm heraus gefunden und damit gearbeitet. Ging bislang immer. Es heißt, ein Browser würde das genau so machen -> erstmal den Request versuchen und erst beim 401 Fehler den Realm ermitteln und beim zweiten Versuch benutzen.

Nun habe ich hier eine Website, deren Realm ständig wechselt... Also bei jedem `urllib2.urlopen(request)` bekomme ich den HTTP 401 Error und als Header sowas hier:

Code: Alles auswählen

Basic realm="	÷V"
Basic realm="	÷? "
Basic realm="	÷PH"
Was mache ich da? Sobald ich einen zweiten Request starte, habe ich ja wieder einen anderen Realm... Teufelskreis! Der Browser allerdings hat damit keine Probleme. Es poppt wie gewöhnlich die Benutzername-/Passwort-Eingabe auf und man kommt normal rein.

Was mache ich falsch?
lunar

Hast du die Kommunikation des Webbrowsers mal beobachtet? Der Webbrowser macht nämlich nichts magisches, sondern liest genau wie du das Realm aus dem Header. Ich würde also mal ganz stark darauf tippen, dass beim Parsen des Headers im Skript irgendwas schief läuft.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Hum, ich parse ja nichts. Ich mache einfach nur:

Code: Alles auswählen

try:
    urllib2.urlopen(url)
except Exception, e:
    print e.headers["www-authenticate"]
Wenn ich das Script nacheinander aufrufe oder dasselbe in einer Schleife mache, dann steht jedes Mal was anderes da (s.o.): Basic realm="..."

Normalerweise steht dort für dieselbe URL auch immer dasselbe Realm da, in meinem kuriosem Fall aber nicht. Frage 1: Wieso macht man sowas? Frage 2: Wieso beherrscht das der Browser? Frage 3: Wie beherrsche ich es?
lunar

droptix hat geschrieben:Frage 1: Wieso macht man sowas?
Man macht sowas gar nicht. Es wäre möglich, dass der Websitenbetreiber irgendwas krudes mit Cookies oder ähnlichem macht, aber das kann ich mir kaum vorstellen.
Frage 2: Wieso beherrscht das der Browser?
Wieso findest du das nicht raus, in dem du – wie ich dir schon gesagt hatte – den Browserverkehr einfach mal mitschneidest. Für den Feuerfuchs gibt es die "Live HTTP Headers"-Erweiterung, oder natürlich Wireshark.
Frage 3: Wie beherrsche ich es?
In dem du die Ergebnisse der Lösung für (2) in dein Skript einbaust.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Wireshark hab ich sogar noch installiert... kannst du mir sagen, wo ich genauer hinschauen sollte? Der Realm sollte ja irgendwo mit drin stehen. Das mit Live HTTP Headers ist auch sehr geil, das versuch ich heute mal.

An Cookies habe ich noch gar nicht gedacht, könnte aber sein.

Wegen der Website: Es ist die Website meines Druckers, also sein "Web-Interface" wie man so schön sagt. Ich möchte dort meine Zähler auslesen. Wenn ich mich das erste mal authentifizieren muss, habe ich immer einen festen Realm-String -- Basic Auth klappt auch immer. Erst wenn nach dem Login ich auf dieser Seite ein Formular abschicke, um auf Seite 2 zu gelangen, ändert sich der Realm so komisch. Es passieren noch ein paar JavaScript Dinge, vielleicht sollte ich JS mal abschalten.

Danke soweit!
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

droptix hat geschrieben:Wegen der Website: Es ist die Website meines Druckers, also sein "Web-Interface" wie man so schön sagt.
Also bei Routern wird gern viel mit JavaScript gearbeitet. Um CPU Last auf den Client zu verlagern. Evtl. macht der Drucker sowas auch?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Cookies werden von der Website nicht verwendet.

Da ich hier trotzdem nicht so richtig weiter komme, frag ich mal anders:

Das sind die Header, die mir LiveHTTPHeaders (Firefox AddOn) beim Abschicken des Formulars ausgibt:

Code: Alles auswählen

http://192.168.1.192/foo.cmd

POST /foo.cmd HTTP/1.1
Host: 192.168.1.192
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9) Gecko/2008052906 Firefox/3.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://192.168.1.192/bar.htm
Authorization: Basic XXXXXXXXXXXXXXXXXX==
Content-Type: application/x-www-form-urlencoded
Content-Length: 5
NUM=1
HTTP/1.x 200 OK
Content-Type: text/html
Cache-Control: no-cache
Pragma: no-cache
Ich habe also zuerst http://192.168.1.192/bar.htm besucht (Referrer) und mich dort authentifizieren müssen. Das klappt auch mit Python, weil der Realm dort ein statischer String ist. Dann schicke ich von dort aus ein HTML-Formular über POST an http://192.168.1.192/foo.cmd ab (keine Ahnung, wieso das "cmd" heißt). Das scheitert bei Python mit 401 Error. Die von LiveHTTPHeaders aufgezeichneten Headerdaten liefern keine Infos über den benutzen Realm. Firefox benutzt intern wahrscheinlich eine Art Passwortmanager und verwendet die Zugangsdaten von bar.html auch für foo.cmd. Wenn ich das in Python versuche, scheitere ich.

Vielleicht muss ich den vorherigen Schritt über bar.htm gar nicht gehen. Vielleicht würde es auch klappen, wenn ich das Formular in Python nachbilde und es inkl. Authentifizierung direkt an foo.cmd schicke? Kann ich die Authentifizerung nicht auch allein über Header abwickeln? Hat jemand ein Beispiel dafür... das würde mich sicher weiter bringen.
lunar

Also dein Ablauf ist wie folgt:
1. GET http://192.168.1.192/bar.htm
2. Zurück kommt 401 Unauthorized mit dem Realm "Foobar"
3. GET http://192.168.1.192/bar.htm mit entsprechenden Authentifizierungsdaten
4. POST http://192.168.1.192/foo.cmd

Wie genau führst du Schritt 3 durch? Bastelst du das manuell zusammen, oder nutzt du dafür urllib2?
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Also da das alles in diesem speziellen Fall nicht funktionieren will (es ging gestern mal kurz, nach einer Weile dann aber nicht mehr, keine Ahnung was ich da falsch gemacht habe), habe ich einen anderen Tipp verfolgt, indem ich die Authentifizierung einfach "manuell" und ohne urllib2.HTTPBasicAuthHandler mache. Es war allerdings noch unbedingt nötig, einen zusätzlichen Header (application/x-www-form-urlencoded) mitzuschicken.

Code: Alles auswählen

    def get_html(self, data=None):
        if data:
            data = urllib.urlencode(data)
        url = "http://192.168.1.192/foo.cmd"
        request = urllib2.Request(url, data)
        request.add_header("Content-Type", "application/x-www-form-urlencoded")
        request.add_header("Authorization", "Basic %s" % base64.encodestring("%s:%s" % (self._user, self._passwd)))
        try:
            handle = urllib2.urlopen(request)
        except urllib2.HTTPError, e:
            handle = None
            print e
        if handle:
            html = handle.read()
            handle.close()
            return html
        return False
Antworten