環境
- Python3.6.5
- requestsモジュール 2.19.1
- Windows10
やりたいこと
Pythonのrequestsモジュールを使って、認証が必要なサイトにアクセスしたいです。
アクセスしたいサイト(以下、サイトA)での認証は、以下の手順で行われます。
- クライアントはサーバに、ログイン用のリクエストを投げる。ログイン用のリクエストにはユーザIDとパスワードを渡す。
⇒ サーバからトークンを受け取る。 - クライアントはサーバに、認証が必要なリクエストを投げる。その際
Authorization
ヘッダにトークンを設定する。
⇒ サーバからレスポンスを受け取る。
実施したこと
Authorization
ヘッダの設定
手順2の処理は、以下のようなコードを書きました。
import requests
# サイトAのURL
sample_url = "https://example.com"
sample_token = "SAMPLE_TOKEN"
res = requests.get(sample_url, headers={"Authorization": sample_token})
ユーザIDとパスワードを.netrc
に記載
サイトAのユーザIDとパスワードは、%USERPROFILE%\.netrc
に書きました。
.netrc
はfptコマンドのための設定ファイルです。
このファイルにユーザIDとパスワードを書いておくと、fptコマンドを実行するときに自動でログインされます。https://linuxjm.osdn.jp/html/netkit/man5/netrc.5.html
また、fptコマンドだけでなくgitコマンドでも自動ログインされます。
https://qiita.com/azusanakano/items/8dc1d7e384b00239d4d9
Pythonには、標準モジュールに.netrc
ファイルを扱うnetrcモジュールがあります。
パスワードをファイルに保存する場合、.netrc
でなく、自分で指定した設定ファイルに書くという選択肢もありましたが、なんとなく.netrc
の方が扱いやすいと思い、.netrc
を選択しました。1
.netrc
ファイルの中身と、netrc
を読み込むPythonのコードです。
machine example.com
login xxxxxx
password yyyyyy
netrc_hosts = netrc.netrc().hosts
host = netrc_hosts['example.com']
user_id = host[0]
password = host[2]
login(user_id, password)
問題:Authorizationヘッダが設定されない
トークンをAuthorization
ヘッダに設定しているのにもかかわらず、認証エラーが発生しました。
リクエストのAuthorizationヘッダを確認したところ、 'Basic eHh4eHh4Onl5eXl5eQ=='
という値で、"SAMPLE_TOKEN"
ではありませんでした。
import requests
# サイトA
sample_url = "https://example.com"
sample_token = "SAMPLE_TOKEN"
##### `.netrc`にユーザ情報が記載されているとき
res = requests.get(sample_url, headers={"Authorization": sample_token})
print(res.request.headers['Authorization'])
# ⇒ 'Basic eHh4eHh4Onl5eXl5eQ=='
##### `.netrc`にユーザ情報が記載されていないとき
res = requests.get(sample_url, headers={"Authorization": sample_token})
print(res.request.headers['Authorization'])
# ⇒ 'SAMPLE_TOKEN'
AuthorizationヘッダのBasic
は、Basic認証を表します。
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Authorization
Basic
以降の文字列をBase64でデコードすると、ユーザIDとパスワードが表示されました。
import base64
print(base64.b64decode("eHh4eHh4Onl5eXl5eQ=="))
# ⇒ b'xxxxxx:yyyyyy'
.netrc
に記載されたユーザIDとパスワードで、Basic認証ヘッダが送信されたようです。
原因
requestsモジュールの仕様でした。
If no authentication method is given with the auth argument, Requests will attempt to get the authentication credentials for the URL’s hostname from the user’s netrc file. The netrc file overrides raw HTTP authentication headers set with headers=.
If credentials for the hostname are found, the request is sent with HTTP Basic Auth.
- 引数
auth
に認証メソッドが渡されない場合、netrcファイルから認証情報を取得する - netrcファイルは、認証ヘッダを上書きする
- ホスト名に対応する認証が見つかったら、HTTP Basic認証でリクエストが送られる
今回のコードでは、引数auth
を使っていなかったため、netrcファイルの認証情報が優先されました。
解決方法
1. auth
引数
認証クラスを作成して、そのクラスのインスタンスをauth
引数に渡す
from requests.auth import AuthBase
class SimpleAuth(AuthBase):
def __init__(self, token):
self.token = token
def __call__(self, r):
r.headers['Authorization'] = self.token
return r
##### `.netrc`にユーザ情報が記載されているとき
res = requests.get(sample_url, auth=SimpleAuth(sample_token))
print(res.request.headers['Authorization'])
# ⇒ 'SAMPLE_TOKEN'
http://docs.python-requests.org/en/master/user/advanced/#custom-authentication 参照
2. Session
クラスのtrust_env
Session
クラスのtrust_env をFalse
にすると、.netrc
を無視します。
Trust environment settings for proxy configuration, default authentication and similar.
s = requests.Session()
s.trust_env = False
res = s.get(sample_url, headers={"Authorization": sample_token})
Session
インスタンスでしか設定できないので、根本的に解決する場合は、解決方法1が良いです。
まとめ
デフォルトで「netrcの設定ファイルを読み込む」というよく分からない仕様にハマりました。
この仕様は、GitHubのイシューでも議論されていました。
However, here's a framework I'd consider for handling auth in the 3.0 branch. I'd welcome a PR to make this the case.
- If the user sets an Authorization header themselves, either via the request or on the Session, we don't bother to look at the netrc file.
- If they didn't, we look at the netrc file for basic auth.
- If we get redirected, we fall back to only looking at the netrc file (which we already currently do).
もしかしたら、バージョン3では「Authorizationヘッダが設定されていれば、netrcを見ない」という仕様になっているかもしれません。
備考
認証情報をnetrc設定ファイルに書くことについて
「Twelve-Factor App」というSoftware as a Serviceを作り上げるための方法論には、「設定を環境変数に格納する」という考えがあります。
Twelve-Factor Appは設定を 環境変数 に格納する。 環境変数は、コードを変更することなくデプロイごとに簡単に変更できる。設定ファイルとは異なり、誤ってリポジトリにチェックインされる可能性はほとんどない。また、独自形式の設定ファイルやJava System Propertiesなど他の設定の仕組みとは異なり、環境変数は言語やOSに依存しない標準である。
そもそも、認証情報をnetrc設定ファイルに書かない方がよさそうです。
-
パスワードを平文で保存しているこ書いていていることには、目をつぶってください… ↩