Базовая аутентификация HTTP не работает в python 3.4

Я пытаюсь войти в REST API с помощью HTTP Basic Authentication, но он не работает и дает ошибку

HTTP error 400: Bad Request

Вот мой код:

import urllib.parse
import urllib.request
import urllib.response

# create an authorization handler
#auth_handler = urllib.request.HTTPPasswordMgrWithDefaultRealm()
auth_handler = urllib.request.HTTPBasicAuthHandler()


# Add the username and password.
# If we knew the realm, we could use it instead of None.

userName = "username"
passWord  = "pass"
top_level_url = "http URL"
auth_handler.add_password(None, top_level_url, userName,passWord)


# create "opener" (OpenerDirector instance)
opener = urllib.request.build_opener(auth_handler)



# Install the opener.
# Now all calls to urllib.request.urlopen use our opener.
urllib.request.install_opener(opener)

# use the opener to fetch a URL
try:
    result = opener.open(top_level_url)
    #result = urllib.request.urlopen(top_level_url)
    messages = result.read()
    print (messages)  
except IOError as e:
    print (e)

Ответ 1

Следующий код python3 будет работать:

import urllib.request
import base64
req = urllib.request.Request(download_url)

credentials = ('%s:%s' % (username, password))
encoded_credentials = base64.b64encode(credentials.encode('ascii'))
req.add_header('Authorization', 'Basic %s' % encoded_credentials.decode("ascii"))

with urllib.request.urlopen(req) as response, open(out_file_path, 'wb') as 
out_file:
    data = response.read()
    out_file.write(data)

Ответ 2

Обновлен для совместимости с Python 3.x.

Библиотека запросов предлагает гораздо более простой способ сделать такой запрос:

import requests

response = requests.get('http://service.example.com',
                        auth=requests.auth.HTTPBasicAuth(
                          'username',
                          'password'))
print(response.text)

В моем собственном тестировании это работает нормально, в то время как решение, включающее urllib.request (например, ваше или использующее дословный код из примеров в документации), не сможет отправить заголовок Authentication:.

Ответ 3

Я бы также использовал библиотеку запросов, как рекомендовал larsks, и упрощает HTTP-запросы.

Тем не менее, здесь приведен пример рабочего кода с использованием urllib

import urllib.parse
import urllib.request
import urllib.response

username = "my_username"
password  = "my_password"
top_level_url = "URL"

# create an authorization handler
p = urllib.request.HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, top_level_url, username, password)

auth_handler = urllib.request.HTTPBasicAuthHandler(p)

opener = urllib.request.build_opener(auth_handler)

urllib.request.install_opener(opener)

try:
    result = opener.open(top_level_url)
    messages = result.read()
    print (messages)
except IOError as e:
    print (e)

Еще одна деталь - я попробовал свой собственный образец кода, и я вернул "http 401 unauthorized", что было бы ожидаемым ответом в случае неудачного или отсутствующего auth.

Однако вы утверждаете, что у вас есть плохой запрос http 400, что заставляет меня думать, что у вас либо есть неправильный URL-адрес, либо есть другая проблема.

Ответ 4

urllib.request.HTTPBasicAuthHandler() по умолчанию использует HTTPPasswordMgr. HTTPPasswordMgr содержит карту с паролем из области и top_level_url.

Когда вы выполняете запрос и сервер возвращает 401. Возвращенные заголовки HTTP содержат:

Www-Authenticate: Basic realm="a-value"

HTTPPasswordMgr поиск (пользователь, пароль) для возвращенной области и новый запрос будут отправляться с (пользователем, паролем).

Когда вы пишете:

auth_handler = urllib.request.HTTPBasicAuthHandler()
# Never use None to realm parameter.
auth_handler.add_password(None, top_level_url, userName,passWord)

Вы ожидаете, что сервер отправит область None (но это невозможно). Если вы хотите, чтобы ваш сервер отправил пустую область в Www-Autheader, вы должны использовать

auth_handler.add_password('', top_level_url, userName,passWord)

Вы можете использовать HTTPPasswordMgrWithDefaultRealm вместо HTTPPasswordMgr, чтобы игнорировать возвращенную область:

auth_handler = urllib.request.HTTPBasicAuthHandler(
    urllib.request.HTTPPasswordMgr()
)
auth_handler.add_password(None, top_level_url, userName,passWord))

Если ваш сервер отправляет вам код ответа 400 с вашим примером кода, аутентификация не запрашивалась.