Python urllib2, xác thực HTTP cơ bản và tr.im


84

Tôi đang cố gắng viết một số mã để sử dụng các API tr.im để rút ngắn URL.

Sau khi đọc http://docs.python.org/library/urllib2.html , tôi đã thử:

   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 là 200 (tôi nghĩ nó phải là 202). url hợp lệ, nhưng xác thực HTTP cơ bản dường như không hoạt động vì URL rút gọn không có trong danh sách URL của tôi (tại http://tr.im/?page=1 ).

Sau khi đọc http://www.voidspace.org.uk/python/articles/authentication.shtml#doing-it-properly, tôi cũng đã thử:

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

Nhưng tôi nhận được kết quả tương tự. (response.code là 200 và url hợp lệ, nhưng không được ghi lại trong tài khoản của tôi tại http://tr.im/ .)

Nếu tôi sử dụng các tham số chuỗi truy vấn thay vì xác thực HTTP cơ bản, như sau:

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

... thì url không chỉ hợp lệ mà còn được ghi lại trong tài khoản tr.im của tôi. (Mặc dù response.code vẫn là 200.)

Phải có điều gì đó sai với mã của tôi (và không phải API của tr.im), bởi vì

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

... trả về:

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

... và URL xuất hiện trong danh sách URL của tôi trên http://tr.im/?page=1 .

Và nếu tôi chạy:

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

... một lần nữa, tôi nhận được:

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

Mã ghi chú là 201 và thông báo là "URL tr.im Đã được tạo [yacitus]."

Tôi không được thực hiện xác thực HTTP cơ bản một cách chính xác (trong cả hai lần thử). Bạn có thể phát hiện ra vấn đề của tôi không? Có lẽ tôi nên nhìn và xem những gì đang được gửi qua dây? Tôi chưa bao giờ làm điều đó trước đây. Có API Python nào tôi có thể sử dụng (có lẽ trong pdb) không? Hoặc có công cụ nào khác (tốt nhất là cho Mac OS X) tôi có thể sử dụng không?


2
trang web phải trả về "WWW-Authenticate"và mã 401 trước khi urllib2 (hoặc httplib2) gửi thông tin đăng nhập của bạn. Xem câu trả lời của tôi bên dưới .
Mark Mikofski

Lưu ý: Dịch vụ này dường như không còn tồn tại.
Laurel

Câu trả lời:


246

Điều này dường như hoạt động thực sự tốt (lấy từ một chuỗi khác)

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
Thay vì base64.encodestring và thay thế, sử dụng base64.standard_b64encode
Paweł Polewicz

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

1
Dựa trên câu trả lời này, tôi đã tạo một gói urllib2_prior_auth không có phần phụ thuộc nào bên ngoài stdlib và tôi cố gắng đẩy thay đổi có liên quan sang stdlib .
mcepl

5
Hoặc thậm chí ngắn hơn / tránh nhập khẩu: request.add_header ( 'Authorization', b'Basic '+ (username + b':' + mật khẩu) .encode ( 'base64'))
makapuf

20

Giải pháp thực sự rẻ:

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

(mà bạn có thể quyết định là không phù hợp vì một số lý do, như bảo mật của url)

Ví dụ về 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()

Có bất kỳ lợi ích nào cho việc này so với việc sử dụng các tham số chuỗi truy vấn không?
Daryl Spitzer

1
Daryl: nếu nó hoạt động, tôi sẽ nói rằng đó là một lợi thế có, và có lẽ an toàn hơn các đối số chuỗi truy vấn vì hầu hết các ứng dụng khách http cẩn thận hơn một chút về cách họ xử lý chúng.
Ali Afshar

Tôi có thể sẽ làm điều này (vì vậy bạn sẽ nhận được sự ủng hộ của tôi), nhưng tôi vẫn muốn tìm ra điều gì sai với mã của mình (vì vậy đây sẽ không phải là câu trả lời được chấp nhận của tôi).
Daryl Spitzer

36
Điều này trả về một lỗi ... Không hợp lệURL: cổng không phải số: 'xxxx@api.tr.im/api'
Nick Bolton

5
@nbolton chắc chắn rằng bạn không sử dụng urllib2.urlopen (url)
CantGetANick

13

Hãy xem câu trả lời của bài đăng SO này và cũng xem hướng dẫn xác thực cơ bản này từ hướng dẫn sử dụng thiếu urllib2 .

Để xác thực cơ bản urllib2 hoạt động, phản hồi http phải chứa mã HTTP 401 Không được phép khóa "WWW-Authenticate"có giá trị "Basic", nếu không, Python sẽ không gửi thông tin đăng nhập của bạn và bạn sẽ cần sử dụng Yêu cầu hoặc urllib.urlopen(url)bằng thông tin đăng nhập của bạn trong url hoặc thêm tiêu đề như trong câu trả lời của @ Flowpoke .

Bạn có thể xem lỗi của mình bằng cách đưa urlopenvào khối thử:

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

Điều này đã giúp tôi vì việc in các tiêu đề dẫn tôi đến việc nhận ra rằng tôi đã đánh máy vào lĩnh vực xác thực. +1
freespace

7

Cách được khuyến nghị là sử dụng requestsmô-đun :

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

Đây là một urllib2biến thể dựa trên tương thích với Python 2/3 nguồn duy nhất :

#!/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+ giới thiệuHTTPPasswordMgrWithPriorAuth() cho phép:

..để loại bỏ việc xử lý phản hồi 401 không cần thiết hoặc để gửi thông tin xác thực vô điều kiện theo yêu cầu đầu tiên để giao tiếp với các máy chủ trả về phản hồi 404 thay vì 401 nếu tiêu đề Ủy quyền không được gửi ..

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

Nó rất dễ dàng để thay thế HTTPBasicAuthHandler()với ProxyBasicAuthHandler()nếu cần thiết trong trường hợp này.


4

Tôi đề nghị rằng giải pháp hiện tại là sử dụng gói urllib2_prior_auth của tôi giải quyết vấn đề này khá tốt (tôi làm việc trên việc đưa vào lib tiêu chuẩn.


1
Nó đã được đưa vào Python 3.5 nhưurrlib.request.HTTPBasicPriorAuthHandler
mcepl

3

Áp dụng các giải pháp tương tự như Vấn đề xác thực cơ bản của Python urllib2 .

xem https://stackoverflow.com/a/24048852/1733117 ; bạn có thể phân lớp urllib2.HTTPBasicAuthHandlerđể thêm Authorizationtiêu đề vào mỗi yêu cầu khớp với url đã biết.

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

Không phải là cuộc gọi stripthừa sau khi b64encode?
Mihai Todor

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.