Python urllib2, основна аутентифікація HTTP та tr.im


84

Я розігруюсь, намагаючись написати якийсь код для використання API tr.im для скорочення URL-адреси.

Прочитавши http://docs.python.org/library/urllib2.html , я спробував:

   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 - 200 (я думаю, це має бути 202). url є дійсним, але базова автентифікація HTTP, здається, не спрацювала, оскільки скорочена URL-адреса відсутня в моєму списку URL-адрес ( http://tr.im/?page=1 ).

Прочитавши http://www.voidspace.org.uk/python/articles/authentication.shtml#doing-it-properly, я також спробував:

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

Але я отримую однакові результати. (response.code - 200, а url - дійсний, але не записаний в моєму обліковому записі за адресою http://tr.im/ .)

Якщо я використовую параметри рядка запиту замість базової аутентифікації HTTP, наприклад:

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

... тоді не тільки дійсний url, але він записаний у моєму обліковому записі tr.im. (Хоча response.code все ще 200.)

У моєму коді, мабуть, щось не так (а не API tr.im), оскільки

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

... повертає:

{"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"}

... і URL-адреса дійсно відображається у моєму списку URL-адрес на http://tr.im/?page=1 .

І якщо я біжу:

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

... знову я отримую:

{"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"}

Код примітки - 201, а повідомлення - "tr.im URL уже створений [yacitus]."

Я не повинен правильно виконувати базову аутентифікацію HTTP (за жодної спроби). Ви можете помітити мою проблему? Можливо, мені слід подивитися і побачити, що надсилається по дроту? Я ніколи раніше цього не робив. Чи є API-інтерфейси Python, якими я можу скористатися (можливо, в pdb)? Або є інший інструмент (бажано для Mac OS X), яким я можу скористатися?


2
сайт повинен повернути "WWW-Authenticate"код 401, перш ніж urllib2 (або httplib2) надішле ваші дані. Дивіться мою відповідь нижче .
Марк Мікофскі 15.03.12

Примітка: Ця послуга, здається, не працює.
Лорел

Відповіді:


246

Здається, це працює дуже добре (взято з іншої теми)

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)

7
Замість base64.encodestring і заміни використовуйте base64.standard_b64encode
Paweł Polewicz

5
request.add_header('Authorization', b'Basic ' + base64.b64encode(username + b':' + password))
jfs

1
На основі цієї відповіді я створив пакет urllib2_prior_auth, який не має залежностей поза stdlib, і я намагаюся перенести відповідну зміну в stdlib .
mcepl

5
Або навіть коротше / уникаючи імпорту: request.add_header ('Авторизація', b'Basic '+ (ім'я користувача + b': '+ пароль) .encode (' base64 '))
makapuf

20

Дійсно дешеве рішення:

urllib.urlopen('http://user:xxxx@api.tr.im/api')

(який, на вашу думку, не підходить з ряду причин, таких як безпека URL-адреси)

Приклад API Github :

>>> import urllib, json
>>> result = urllib.urlopen('https://personal-access-token:x-oauth-basic@api.github.com/repos/:owner/:repo')
>>> r = json.load(result.fp)
>>> result.close()

Чи є в цьому переваги перед використанням параметрів рядка запиту?
Даріл Спітцер,

1
Даріл: якщо це працює, я б сказав, що це перевага так, і, мабуть, більш безпечне, ніж аргументи рядка запиту, оскільки більшість клієнтів http трохи обережніше, як вони з ними обробляються.
Алі Афшар,

Я, мабуть, піду з цим (так що ви отримаєте мою підтримку), але я все одно хотів би з'ясувати, що не так з моїм кодом (тому це не буде моєю прийнятою відповіддю).
Дарил Спітцер,

36
Це повертає помилку ... InvalidURL: нечисловий порт: 'xxxx@api.tr.im/api'
Нік Болтон,

5
@nbolton переконайтесь, що ви не використовуєте urllib2.urlopen (url)
CantGetANick

13

Погляньте на цю відповідь на публікацію SO, а також ознайомтеся з цим основним підручником з автентифікації в посібнику з відсутніми urllib2 .

Щоб основна автентифікація urllib2 працювала, відповідь http повинна містити HTTP-код 401 Несанкціонований та ключ "WWW-Authenticate"зі значенням, "Basic"інакше Python не надсилатиме вашу інформацію для входу, і вам потрібно буде або використовувати Requests , або urllib.urlopen(url)ваш логін у URL, або додати заголовок , як в @ Flowpoke в відповідь .

Ви можете переглянути свою помилку, помістивши її urlopenв блок спроби:

try:
    urllib2.urlopen(urllib2.Request(url))
except urllib2.HTTPError, e:
    print e.headers
    print e.headers.has_key('WWW-Authenticate')

Це допомогло мені, тому що друк заголовків змусив мене зрозуміти, що я набрав область автентифікації. +1
простір

7

Рекомендований спосіб - використовувати requestsмодуль :

#!/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

Ось один вихідний urllib2варіант, заснований на сумісності з Python 2/3 :

#!/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+ представляє,HTTPPasswordMgrWithPriorAuth() що дозволяє:

..усунути непотрібну обробку відповідей 401 або безумовно надіслати облікові дані за першим запитом, щоб зв’язатися із серверами, які повертають відповідь 404 замість 401, якщо заголовок авторизації не надіслано ..

#!/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()

У цьому випадку його легко замінити HTTPBasicAuthHandler()на, ProxyBasicAuthHandler()якщо це необхідно.


4

Я б запропонував, що поточним рішенням є використання мого пакета urllib2_prior_auth, який це досить добре вирішує (я працюю над включенням до стандартної бібліотеки.


1
Він був включений до Python 3.5 якurrlib.request.HTTPBasicPriorAuthHandler
mcepl

3

Застосовуються ті самі рішення, що й Python urllib2 Basic Auth Problem .

див. https://stackoverflow.com/a/24048852/1733117 ; Ви можете підклас, urllib2.HTTPBasicAuthHandlerщоб додати Authorizationзаголовок до кожного запиту, який відповідає відомій URL-адресі.

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

Чи не є виклик stripзайвим після b64encode?
Mihai Todor

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.