Python urllib2 การพิสูจน์ตัวตน HTTP พื้นฐานและ tr.im


85

ฉันกำลังเล่นอยู่พยายามเขียนโค้ดเพื่อใช้ไฟล์ tr.im API เพื่อย่อ 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 พื้นฐานอย่างถูกต้อง (ไม่ว่าจะพยายามก็ตาม) คุณมองเห็นปัญหาของฉันได้ไหม บางทีฉันควรมองและดูว่ามีอะไรถูกส่งผ่านสาย? ฉันไม่เคยทำแบบนั้นมาก่อน มี Python API ที่ฉันสามารถใช้ได้หรือไม่ (อาจเป็นใน pdb) หรือมีเครื่องมืออื่น (โดยเฉพาะสำหรับ Mac OS X) ที่ฉันสามารถใช้ได้?


2
ไซต์ต้องส่งคืน"WWW-Authenticate"และรหัส 401 ก่อนที่ urllib2 (หรือ httplib2) จะส่งข้อมูลรับรองของคุณ ดูคำตอบของฉันด้านล่าง
Mark Mikofski

หมายเหตุ: บริการนี้ดูเหมือนจะหมดอายุ
Laurel

คำตอบ:


247

ดูเหมือนว่าจะทำงานได้ดีจริงๆ (นำมาจากหัวข้ออื่น)

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 ('Authorization', b'Basic '+ (username + b': '+ password) .encode (' base64 '))
makapuf

20

วิธีแก้ปัญหาราคาถูกจริงๆ:

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

(ซึ่งคุณอาจตัดสินใจว่าไม่เหมาะสมด้วยเหตุผลหลายประการเช่นความปลอดภัยของ url)

ตัวอย่าง Github API :

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

มีข้อดีมากกว่าการใช้พารามิเตอร์สตริงการสืบค้นหรือไม่?
Daryl Spitzer

1
Daryl: ถ้ามันใช้งานได้ฉันจะบอกว่ามันเป็นข้อดีใช่และอาจปลอดภัยกว่าอาร์กิวเมนต์สตริงการค้นหาเนื่องจากไคลเอนต์ http ส่วนใหญ่ระมัดระวังในการจัดการกับพวกเขา
Ali Afshar

ฉันอาจจะไปกับสิ่งนี้ (เพื่อให้คุณได้รับการโหวตเพิ่มขึ้น) แต่ฉันก็ยังอยากรู้ว่ามีอะไรผิดปกติกับรหัสของฉัน (ดังนั้นนี่จะไม่ใช่คำตอบที่ฉันยอมรับ)
Daryl Spitzer

36
สิ่งนี้ส่งคืนข้อผิดพลาด ... InvalidURL: nonnumeric port: 'xxxx@api.tr.im/api'
Nick Bolton

5
@nbolton ให้แน่ใจว่าคุณไม่ได้ใช้ urllib2.urlopen (url)
CantGetANick

13

ดูคำตอบของโพสต์ SO นี้และดูบทแนะนำการตรวจสอบสิทธิ์พื้นฐานจากคู่มือที่ขาดหายไปของ urllib2หายคู่มือ

เพื่อให้การพิสูจน์ตัวตนพื้นฐานของ urllib2 ทำงานได้การตอบสนอง http ต้องมีรหัส HTTP 401 ไม่ได้รับอนุญาตและคีย์ที่"WWW-Authenticate"มีค่า"Basic"มิฉะนั้น Python จะไม่ส่งข้อมูลการเข้าสู่ระบบของคุณและคุณจะต้องใช้คำขอหรือ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
FreeSpace

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ของฉันซึ่งแก้ปัญหานี้ได้ค่อนข้างดี (ฉันทำงานเพื่อรวมเข้ากับ lib มาตรฐาน


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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.