Python urllib2,基本HTTP身份验证和tr.im


84

我在玩耍,尝试编写一些代码以使用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()

...那么网址不仅有效,而且记录在我的tr.im帐户中。(尽管response.code仍然是200。)

我的代码(而不是tr.im的API)一定有问题,因为

$ 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确实出现在http://tr.im/?page=1上的URL列表中。

如果我运行:

$ 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"并且在urllib2(或httplib2)发送您的凭据之前,代码为401。请参阅下面的答案
Mark Mikofski 2012年

注意:此服务似乎已失效。
月桂树

Answers:


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的
帕维尔Polewicz

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

1
基于此答案,我创建了一个软件包urllib2_prior_auth,该软件包在stdlib之外没有任何依赖关系,我尝试将相关更改推送到stdlib
mcepl 2014年

5
甚至更短/避免导入:request.add_header('Authorization',b'Basic'+(username + b':'+ password).encode('base64'))
makapuf

20

真正便宜的解决方案:

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

(出于多种原因,您可能会决定不适合该网址,例如网址的安全性)

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

与使用查询字符串参数相比,这有什么好处吗?
达里尔·斯皮策

1
Daryl:如果可行,我会说这是一个优势,并且可能比查询字符串参数更安全,因为大多数http客户端在处理它们时都更加谨慎。
阿里·阿夫沙尔

我可能会这样做(这样您就可以投票了),但是我仍然想弄清楚我的代码出了什么问题(因此这不是我接受的答案)。
达里尔·斯皮策

36
这将返回一个错误... InvalidURL:非数字端口:“xxxx@api.tr.im/api”
尼克·博尔顿

5
@nbolton确保您未使用
urllib2.urlopen

13

看看这个SO后的答案,也看看这个基本认证教程urllib2的失踪手册

为了使urllib2基本身份验证正常工作,http响应必须包含HTTP代码401 Unauthorized"WWW-Authenticate"带有值的密钥,"Basic"否则,Python将不会发送您的登录信息,您将需要使用Requestsurllib.urlopen(url)在url或添加类似@Flowpoke的 answer中的标头。

您可以通过将您的错误放在urlopentry块中来查看错误:

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

这对我有所帮助,因为打印标头使我意识到我输入了身份验证领域。+1
自由空间

7

推荐的方法是使用requestsmodule

#!/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()必要时在这种情况下。



3

适用与Python urllib2基本身份验证问题相同的解决方案。

参见https://stackoverflow.com/a/24048852/1733117 ; 您可以使用子类urllib2.HTTPBasicAuthHandlerAuthorization标头添加到与已知网址匹配的每个请求中。

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 2015年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.