Python urllib2、基本HTTP認証、およびtr.im


84

私は遊んでいて、tr.imAPIを 使用して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は有効ですが、短縮URLが私のURLリスト(http://tr.im/?page=1)にないため、基本HTTP認証が機能していないようです。

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ですが。)

私のコードには何か問題があるはずです(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 Already Created [yacitus]」です。

基本HTTP認証を正しく行ってはいけません(どちらの試みでも)。私の問題を見つけられますか?おそらく、私はネットワークを介して送信されているものを見て確認する必要がありますか?私は前にそれをしたことがありません。使用できるPythonAPIはありますか(おそらくpdbで)?または、使用できる別のツール(Mac OS X用が望ましい)はありますか?


2
"WWW-Authenticate"urllib2(またはhttplib2)が資格情報を送信する前に、サイトが戻ってコード401を送信する必要があります。以下の私の答えを参照してください
Mark Mikofski 2012年

注:このサービスは機能していないようです。
ローレル

回答:


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

1
この回答に基づいて、stdlibの外部に依存関係のないパッケージurllib2_prior_authを作成し、関連する変更をstdlibプッシュしようとしています
mcepl 2014年

5
またはさらに短い/インポートの回避:request.add_header( 'Authorization'、b'Basic '+(username + b': '+ password).encode(' base64 '))
makapuf 2016年

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

クエリ文字列パラメータを使用するよりも、これに利点はありますか?
ダリルスピッツァー

1
ダリル:それが機能する場合、それは利点であり、クエリ文字列引数よりもおそらく安全であると言えます。ほとんどのhttpクライアントはそれらの処理方法に少し注意を払っています。
Ali Afshar

私はおそらくこれで行くでしょう(あなたが私の賛成票を得るように)、しかし私はまだ私のコードの何が悪いのかを理解したいと思います(それでこれは私の受け入れられた答えではありません)。
ダリルスピッツァー

36
これはエラーを返します... InvalidURL:nonnumeric port:'xxxx@api.tr.im/api '
Nick Bolton

5
@nbolton urllib2.urlopen(url)を使用していないことを確認してください
CantGetANick 2011年

13

見てみましょう。このSOポストの答えをしても、これを見て、基本的な認証チュートリアルからマニュアル行方不明urllib2の

仕事へのurllib2の基本的な認証のためのためには、HTTPレスポンスが不正なHTTPコード401を含んでいなければならないし、キー"WWW-Authenticate"値を持つ"Basic"そうでない場合、Pythonはあなたのログイン情報を送信しません、あなたはどちらか使用する必要があります要求、またはurllib.urlopen(url)中にログインしてurlするか、@ Flowpokeの 回答のようにヘッダーを追加します

urlopentryブロックに入れると、エラーを表示できます。

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

ヘッダーを印刷すると、認証レルムをタイプミスしたことに気付くので、これは私を助けました。+1
フリースペース2012年

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

これは、単一ソースのPython2 / 3互換urllib2ベースのバリアントです。

#!/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応答処理を排除するため、またはAuthorizationヘッダーが送信されない場合に401ではなく404応答を返すサーバーと通信するために、最初の要求で資格情報を無条件に送信するため。

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

Pythonurllib2の基本認証問題と同じ解決策が適用されます。

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.