Python urllib2, grundlegende HTTP-Authentifizierung und tr.im.

84

Ich spiele herum und versuche, Code zu schreiben, um mithilfe der tr.im- APIs eine URL zu verkürzen.

Nachdem ich http://docs.python.org/library/urllib2.html gelesen hatte , versuchte ich:

   TRIM_API_URL = 'http://api.tr.im/api'
   auth_handler = urllib2.HTTPBasicAuthHandler()
   auth_handler.add_password(realm='tr.im',
                             uri=TRIM_API_URL,
                             user=USERNAME,
                             passwd=PASSWORD)
   opener = urllib2.build_opener(auth_handler)
   urllib2.install_opener(opener)
   response = urllib2.urlopen('%s/trim_simple?url=%s'
                              % (TRIM_API_URL, url_to_trim))
   url = response.read().strip()

response.code ist 200 (ich denke, es sollte 202 sein). Die URL ist gültig, aber die grundlegende HTTP-Authentifizierung scheint nicht funktioniert zu haben, da die verkürzte URL nicht in meiner URL-Liste enthalten ist (unter http://tr.im/?page=1 ).

Nachdem ich http://www.voidspace.org.uk/python/articles/authentication.shtml#doing-it-properly gelesen hatte, versuchte ich auch:

   TRIM_API_URL = 'api.tr.im/api'
   password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
   password_mgr.add_password(None, TRIM_API_URL, USERNAME, PASSWORD)
   auth_handler = urllib2.HTTPBasicAuthHandler(password_mgr)
   opener = urllib2.build_opener(auth_handler)
   urllib2.install_opener(opener)
   response = urllib2.urlopen('http://%s/trim_simple?url=%s'
                              % (TRIM_API_URL, url_to_trim))
   url = response.read().strip()

Aber ich bekomme die gleichen Ergebnisse. (response.code ist 200 und die URL ist gültig, aber nicht in meinem Konto unter http://tr.im/ gespeichert .)

Wenn ich anstelle der grundlegenden HTTP-Authentifizierung Abfragezeichenfolgenparameter verwende, wie folgt:

   TRIM_API_URL = 'http://api.tr.im/api'
   response = urllib2.urlopen('%s/trim_simple?url=%s&username=%s&password=%s'
                              % (TRIM_API_URL,
                                 url_to_trim,
                                 USERNAME,
                                 PASSWORD))
   url = response.read().strip()

... dann ist nicht nur die URL gültig, sondern sie wird auch in meinem tr.im-Konto aufgezeichnet. (Obwohl response.code immer noch 200 ist.)

Es muss jedoch etwas mit meinem Code nicht stimmen (und nicht mit der API von tr.im), weil

$ curl -u yacitus:xxxx http://api.tr.im/api/trim_url.json?url=http://www.google.co.uk

...kehrt zurück:

{"trimpath":"hfhb","reference":"nH45bftZDWOX0QpVojeDbOvPDnaRaJ","trimmed":"11\/03\/2009","destination":"http:\/\/www.google.co.uk\/","trim_path":"hfhb","domain":"google.co.uk","url":"http:\/\/tr.im\/hfhb","visits":0,"status":{"result":"OK","code":"200","message":"tr.im URL Added."},"date_time":"2009-03-11T10:15:35-04:00"}

... und die URL erscheint in meiner Liste der URLs unter http://tr.im/?page=1 .

Und wenn ich renne:

$ curl -u yacitus:xxxx http://api.tr.im/api/trim_url.json?url=http://www.google.co.uk

... wieder bekomme ich:

{"trimpath":"hfhb","reference":"nH45bftZDWOX0QpVojeDbOvPDnaRaJ","trimmed":"11\/03\/2009","destination":"http:\/\/www.google.co.uk\/","trim_path":"hfhb","domain":"google.co.uk","url":"http:\/\/tr.im\/hfhb","visits":0,"status":{"result":"OK","code":"201","message":"tr.im URL Already Created [yacitus]."},"date_time":"2009-03-11T10:15:35-04:00"}

Der Hinweiscode lautet 201 und die Nachricht lautet "tr.im URL Already Created [yacitus]".

Ich muss die grundlegende HTTP-Authentifizierung nicht korrekt durchführen (in beiden Versuchen). Kannst du mein Problem erkennen? Vielleicht sollte ich schauen und sehen, was über das Kabel gesendet wird? Das habe ich noch nie gemacht. Gibt es Python-APIs, die ich verwenden kann (möglicherweise in pdb)? Oder gibt es ein anderes Tool (vorzugsweise für Mac OS X), das ich verwenden kann?

Daryl Spitzer
quelle
2
Die Site muss zurückkehren "WWW-Authenticate"und den Code 401 eingeben, bevor urllib2 (oder httplib2) Ihre Anmeldeinformationen sendet. Siehe meine Antwort unten .
Mark Mikofski
Hinweis: Dieser Dienst scheint nicht mehr verfügbar zu sein.
Laurel

Antworten:

246

Dies scheint wirklich gut zu funktionieren (aus einem anderen Thread entnommen)

import urllib2, base64

request = urllib2.Request("http://api.foursquare.com/v1/user")
base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
request.add_header("Authorization", "Basic %s" % base64string)   
result = urllib2.urlopen(request)
Ben Keating
quelle
7
Anstelle von base64.encodestring und Ersetzen verwenden Sie base64.standard_b64encode
Paweł Polewicz
5
request.add_header('Authorization', b'Basic ' + base64.b64encode(username + b':' + password))
JFS
1
Basierend auf dieser Antwort habe ich ein Paket urllib2_prior_auth erstellt, das keine Abhängigkeiten außerhalb von stdlib aufweist, und ich versuche , die relevante Änderung auf stdlib zu übertragen .
Mcepl
5
Oder noch kürzer / Import vermeiden: request.add_header ('Authorization', b'Basic '+ (Benutzername + b': '+ Passwort) .encode (' base64 '))
makapuf
20

Wirklich günstige Lösung:

urllib.urlopen('http://user:[email protected]/api')

(was Sie möglicherweise entscheiden, ist aus einer Reihe von Gründen nicht geeignet, wie z. B. der Sicherheit der URL)

Beispiel für eine Github-API :

>>> import urllib, json
>>> result = urllib.urlopen('https://personal-access-token:[email protected]/repos/:owner/:repo')
>>> r = json.load(result.fp)
>>> result.close()
Ali Afshar
quelle
Gibt es Vorteile gegenüber der Verwendung von Abfragezeichenfolgenparametern?
Daryl Spitzer
1
Daryl: Wenn es funktioniert, würde ich sagen, dass es ein Vorteil ist, ja, und wahrscheinlich sicherer als Argumente für Abfragezeichenfolgen, da die meisten http-Clients etwas vorsichtiger damit umgehen.
Ali Afshar
Ich werde wahrscheinlich damit weitermachen (damit Sie meine positive Bewertung erhalten), aber ich möchte trotzdem herausfinden, was mit meinem Code nicht stimmt (dies ist also nicht meine akzeptierte Antwort).
Daryl Spitzer
36
Dies gibt einen Fehler zurück ... InvalidURL: nicht numerischer Port: '[email protected]/api'
Nick Bolton
5
@nbolton stellen Sie sicher, dass Sie nicht urllib2.urlopen (url)
CantGetANick
13

Schauen Sie sich diese SO-Post-Antwort an und sehen Sie sich auch dieses grundlegende Authentifizierungs-Tutorial aus dem fehlenden Handbuch für urllib2 an .

Damit die Basisauthentifizierung von urllib2 funktioniert, muss die http-Antwort den nicht autorisierten HTTP-Code 401 und einen Schlüssel "WWW-Authenticate"mit dem Wert "Basic"andernfalls enthalten. Python sendet Ihre Anmeldeinformationen nicht und Sie müssen entweder Requests oder urllib.urlopen(url)Ihre Anmeldung in der URL, oder fügen Sie einen Header wie in der Antwort von @ Flowpoke hinzu .

Sie können Ihren Fehler anzeigen, indem Sie Ihren urlopenin einen Try-Block einfügen:

try:
    urllib2.urlopen(urllib2.Request(url))
except urllib2.HTTPError, e:
    print e.headers
    print e.headers.has_key('WWW-Authenticate')
Mark Mikofski
quelle
Dies hat mir geholfen, da ich durch das Drucken der Header festgestellt habe, dass ich den Authentifizierungsbereich getippt habe. +1
Freiraum
7

Die empfohlene Methode ist die Verwendung des requestsModuls :

#!/usr/bin/env python
import requests # $ python -m pip install requests
####from pip._vendor import requests # bundled with python

url = 'https://httpbin.org/hidden-basic-auth/user/passwd'
user, password = 'user', 'passwd'

r = requests.get(url, auth=(user, password)) # send auth unconditionally
r.raise_for_status() # raise an exception if the authentication fails

Hier ist eine Python 2/3-kompatible urllib2Variante aus einer Hand :

#!/usr/bin/env python
import base64
try:
    from urllib.request import Request, urlopen
except ImportError: # Python 2
    from urllib2 import Request, urlopen

credentials = '{user}:{password}'.format(**vars()).encode()
urlopen(Request(url, headers={'Authorization': # send auth unconditionally
    b'Basic ' + base64.b64encode(credentials)})).close()

Python 3.5+ führt Folgendes einHTTPPasswordMgrWithPriorAuth() :

..um unnötige 401-Antwortverarbeitung zu vermeiden oder bei der ersten Anforderung bedingungslos Anmeldeinformationen zu senden, um mit Servern zu kommunizieren, die eine 404-Antwort anstelle einer 401 zurückgeben, wenn der Autorisierungsheader nicht gesendet wird ..

#!/usr/bin/env python3
import urllib.request as urllib2

password_manager = urllib2.HTTPPasswordMgrWithPriorAuth()
password_manager.add_password(None, url, user, password,
                              is_authenticated=True) # to handle 404 variant
auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)
opener = urllib2.build_opener(auth_manager)

opener.open(url).close()

Es ist leicht zu ersetzen HTTPBasicAuthHandler()mit ProxyBasicAuthHandler()gegebenenfalls in diesem Fall.

jfs
quelle
4

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
1
Es wurde in Python 3.5 asurrlib.request.HTTPBasicPriorAuthHandler
mcepl
3

Es gelten die gleichen Lösungen wie für Python urllib2 Basic Auth Problem .

siehe https://stackoverflow.com/a/24048852/1733117 ; Sie können eine Unterklasse erstellen urllib2.HTTPBasicAuthHandler, um den AuthorizationHeader zu jeder Anforderung hinzuzufügen, die der bekannten URL entspricht.

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
dnozay
quelle
Ist der Anruf danach nicht stripüberflüssig b64encode?
Mihai Todor