Python urllib2 Basic Auth Problem

81

Update: Aufgrund von Lees Kommentar habe ich beschlossen, meinen Code zu einem wirklich einfachen Skript zu verdichten und über die Befehlszeile auszuführen:

import urllib2
import sys

username = sys.argv[1]
password = sys.argv[2]
url = sys.argv[3]
print("calling %s with %s:%s\n" % (url, username, password))

passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, url, username, password)
urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(passman)))

req = urllib2.Request(url)
f = urllib2.urlopen(req)
data = f.read()
print(data)

Leider wird der AuthorizationHeader immer noch nicht generiert (per Wireshark) :(

Ich habe ein Problem beim Senden von AUTH über urllib2. Ich habe mir diesen Artikel angesehen und bin dem Beispiel gefolgt. Mein Code:

passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, "api.foursquare.com", username, password)
urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(passman)))

req = urllib2.Request("http://api.foursquare.com/v1/user")    
f = urllib2.urlopen(req)
data = f.read()

Ich sehe Folgendes auf dem Draht über Wireshark:

GET /v1/user HTTP/1.1
Host: api.foursquare.com
Connection: close
Accept-Encoding: gzip
User-Agent: Python-urllib/2.5 

Sie können sehen, dass die Autorisierung nicht gesendet wird, im Gegensatz dazu, wenn ich eine Anfrage per Curl sende: curl -u user:password http://api.foursquare.com/v1/user

GET /v1/user HTTP/1.1
Authorization: Basic =SNIP=
User-Agent: curl/7.19.4 (universal-apple-darwin10.0) libcurl/7.19.4 OpenSSL/0.9.8k zlib/1.2.3
Host: api.foursquare.com
Accept: */*

Aus irgendeinem Grund scheint mein Code die Authentifizierung nicht zu senden - sieht jemand, was mir fehlt?

Vielen Dank

-simon

Simon
quelle
1
Ich frage mich, ob das Problem darin besteht, dass die Site keinen 'WWW-Authenticate'Header zurückgibt. Sie können dies überprüfen, indem Sie try: urllib2.urlopen(req) except urllib2.HTTPError, e: print e.headers Siehe diese SO-Post-Antwort verwenden .
Mark Mikofski

Antworten:

199

Das Problem könnte sein, dass die Python-Bibliotheken gemäß HTTP-Standard zuerst eine nicht authentifizierte Anfrage senden und dann nur dann die richtigen Anmeldeinformationen gesendet werden, wenn sie mit einem 401-Wiederholungsversuch beantwortet werden. Wenn die Foursquare-Server keine "Standardauthentifizierung" durchführen, funktionieren die Bibliotheken nicht.

Versuchen Sie, Header für die Authentifizierung zu verwenden:

import urllib2, base64

request = urllib2.Request("http://api.foursquare.com/v1/user")
base64string = base64.b64encode('%s:%s' % (username, password))
request.add_header("Authorization", "Basic %s" % base64string)   
result = urllib2.urlopen(request)

Hatte das gleiche Problem wie Sie und fand die Lösung aus diesem Thread: http://forums.shopify.com/categories/9/posts/27662

yayitswei
quelle
HTTP-Fehler 505: HTTP-Version nicht unterstützt; (
Daniel Magnusson
Funktioniert auch mit der Paypal-Authentifizierung (um access_token zu erhalten). Vielen Dank, Kumpel!
DerShodan
3
Beachten Sie, dass Sie base64.b64encodestattdessen einfach anrufen können base64.encodestringund dann die neue Leitung nicht ersetzen müssen.
Trey Stout
Vielen Dank an @TreyStout. Ich habe die Lösung so bearbeitet, dass sie Ihren Vorschlag enthält.
Yayitswei
Ähnliches Problem hier. Im Browser Inhalt der autorisierten Seite geladen und wenn ich auf Abbrechen klicke, kann ich den Inhalt der Passwortseite sehen
Mostafa
5

(Kopieren-Einfügen / angepasst von https://stackoverflow.com/a/24048772/1733117 ).

Zuerst können Sie eine Unterklasse urllib2.BaseHandleroder festlegen urllib2.HTTPBasicAuthHandlerund http_requestso implementieren , dass jede Anforderung den entsprechenden AuthorizationHeader hat.

import urllib2
import base64

class PreemptiveBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
    '''Preemptive basic auth.

    Instead of waiting for a 403 to then retry with the credentials,
    send the credentials if the url is handled by the password manager.
    Note: please use realm=None when calling add_password.'''
    def http_request(self, req):
        url = req.get_full_url()
        realm = None
        # this is very similar to the code from retry_http_basic_auth()
        # but returns a request object.
        user, pw = self.passwd.find_user_password(realm, url)
        if pw:
            raw = "%s:%s" % (user, pw)
            auth = 'Basic %s' % base64.b64encode(raw).strip()
            req.add_unredirected_header(self.auth_header, auth)
        return req

    https_request = http_request

Wenn Sie dann faul sind wie ich, installieren Sie den Handler global

api_url = "http://api.foursquare.com/"
api_username = "johndoe"
api_password = "some-cryptic-value"

auth_handler = PreemptiveBasicAuthHandler()
auth_handler.add_password(
    realm=None, # default realm.
    uri=api_url,
    user=api_username,
    passwd=api_password)
opener = urllib2.build_opener(auth_handler)
urllib2.install_opener(opener)
dnozay
quelle
5

Hier ist, was ich verwende, um ein ähnliches Problem zu beheben, das beim Versuch, auf die MailChimp-API zuzugreifen, aufgetreten ist. Dies macht das gleiche, nur schöner formatiert.

import urllib2
import base64

chimpConfig = {
    "headers" : {
    "Content-Type": "application/json",
    "Authorization": "Basic " + base64.encodestring("hayden:MYSECRETAPIKEY").replace('\n', '')
    },
    "url": 'https://us12.api.mailchimp.com/3.0/'}

#perform authentication
datas = None
request = urllib2.Request(chimpConfig["url"], datas, chimpConfig["headers"])
result = urllib2.urlopen(request)
Hayden Shelton
quelle
4

Der zweite Parameter muss ein URI sein, kein Domänenname. dh

passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, "http://api.foursquare.com/", username, password)
Lee
quelle
1
Danke - ich soll ich versucht , in einer Reihe von verschiedenen Kombinationen hat erwähnt , dass http://api.foursquare.com, api.foursquare.com, http://api.foursquare.com/v1/, aber das scheint nicht das Problem zu lösen.
Simon
Ich habe dies gerade gegen einen lokalen Server hier versucht, der eine grundlegende Authentifizierung erfordert, und mit der URL im add_password hat es gut funktioniert. Ich würde daher vorschlagen, dass etwas anderes im Gange ist.
Lee
Dies funktioniert nur, wenn die http-Antwort den Code 401 Unauthorized und den Header enthält 'WWW-Authenticate'. siehe diese SO Post Antwort .
Mark Mikofski
0

Ich würde vorschlagen, dass die aktuelle Lösung darin besteht, mein Paket urllib2_prior_auth zu verwenden , das dies ziemlich gut löst (ich arbeite an der Aufnahme in die Standardbibliothek.

mcepl
quelle
Wil es erlaubt, URLs wieurllib2.urlopen('http://USER:[email protected]/path/')
ddofborg
Dies ist ein weiteres Problem. Sind Sie sicher, dass dies mit dem Standard nicht funktioniert urllib2?
Mcepl