Problema de autenticación básica de Python urllib2

81

Actualización: basado en el comentario de Lee, decidí condensar mi código en un script realmente simple y ejecutarlo desde la línea de comando:

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)

Desafortunadamente, todavía no generará el Authorizationencabezado (según Wireshark) :(

Tengo un problema para enviar AUTH básica a través de urllib2. Eché un vistazo a este artículo y seguí el ejemplo. Mi código:

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()

Veo lo siguiente en Wire a través de wirehark:

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

Puede ver que la autorización no se envía, en comparación con cuando envío una solicitud a través de curl: 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: */*

Por alguna razón, mi código parece no enviar la autenticación. ¿Alguien ve lo que me estoy perdiendo?

Gracias

-Simón

Simón
fuente
1
Me pregunto si el problema es que el sitio no devuelve un 'WWW-Authenticate'encabezado. Puede verificar esto usando try: urllib2.urlopen(req) except urllib2.HTTPError, e: print e.headers Ver esta respuesta de publicación SO .
Mark Mikofski

Respuestas:

199

El problema podría ser que las bibliotecas de Python, según HTTP-Standard, primero envían una solicitud no autenticada, y luego, solo si se responde con un reintento 401, se envían las credenciales correctas. Si los servidores de Foursquare no realizan una "autenticación totalmente estándar", las bibliotecas no funcionarán.

Intente usar encabezados para realizar la autenticación:

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)

Tuve el mismo problema que tú y encontré la solución en este hilo: http://forums.shopify.com/categories/9/posts/27662

yayitswei
fuente
Error HTTP 505: Versión HTTP no admitida; (
Daniel Magnusson
También funciona con autenticación de PayPal (para recibir access_token). ¡Muchas gracias, amigo!
DerShodan
3
Tenga en cuenta que puede simplemente llamar en base64.b64encodelugar de base64.encodestringy luego no es necesario reemplazar la nueva línea.
Trey Stout
Gracias @TreyStout, edité la solución para incluir tu sugerencia.
yayitswei
Problema similar aquí ... En el contenido del navegador de la página autorizada cargada y si hago clic en el botón Cancelar puedo ver el contenido de la página de contraseña
Mostafa
5

(copiar y pegar / adaptado de https://stackoverflow.com/a/24048772/1733117 ).

Primero puede crear una subclase urllib2.BaseHandlero urllib2.HTTPBasicAuthHandler, e implementarlo http_requestpara que cada solicitud tenga el Authorizationencabezado apropiado .

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

Entonces, si eres vago como yo, instala el controlador globalmente

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
fuente
5

Esto es lo que estoy usando para lidiar con un problema similar que encontré al intentar acceder a la API de MailChimp. Esto hace lo mismo, simplemente formateado mejor.

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
fuente
4

El segundo parámetro debe ser un URI, no un nombre de dominio. es decir

passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, "http://api.foursquare.com/", username, password)
Sotavento
fuente
1
Gracias - debería haber mencionado que he intentado en un número de diferentes combinaciones http://api.foursquare.com, api.foursquare.com, http://api.foursquare.com/v1/, pero que no parece resolver el problema.
Simon
Intenté esto en un servidor local aquí que requiere autenticación básica y con la URL en la contraseña add_pass, funcionó bien. Por tanto, sugeriría que hay algo más en marcha.
Lee
Esto solo funcionará si la respuesta http contiene el código 401 No autorizado y el encabezado 'WWW-Authenticate'; ver esta respuesta de la publicación SO .
Mark Mikofski
0

Sugeriría que la solución actual es usar mi paquete urllib2_prior_auth que resuelve esto bastante bien (trabajo en la inclusión en el estándar lib.

mcepl
fuente
Si permite abrir URL comourllib2.urlopen('http://USER:[email protected]/path/')
ddofborg
Este es otro problema. ¿Estás seguro de que esto no funciona con el estándar urllib2?
mcepl