1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

requests.postのallow_redirectsでハマった話

Posted at

ハマった状況

Flask-Loginを使用した簡単なアプリケーションを作成中。
ブラウザから操作した範囲ではOKであることを確認した。

テストを自動化するために、requestsパッケージを使用して、ログインを実装してみたが、
ログインに成功しているにも関わらず、その後の問合せに対してログイン状態が維持されず、
再度、ログインを要求するレスポンスが返ってくる。

最初のコーディング

作成したアプリケーションは、emailとpasswordで認証する。

import requests
from bs4 import BeautifulSoup

# GET /loginでformを取得して、csrfトークンを抽出
r = requests.get("http://127.0.0.1:5000/login")
doc = BeautifulSoup(r.text, 'html.parser')
csrf_token = doc.find(attrs={'name':'csrf_token'}).get('value')

# credentialとsession cookieを指定して、POST /login
cookies = {"session": r.cookies["session"]}
credential = {"email": "user1@email.com", "password": "password1",
    "csrf_token": csrf_token}
r = requests.post("http://127.0.0.1:5000/login", cookies=cookies,
    data=credential, allow_redirects=True)

しかしながら、この後の呼び出しでは、ログイン状態が維持されていないため、
ログイン画面に飛ばされてしまう。

コマンドラインからcurlで実行した場合は、正しくログイン状態が維持されることは確認。

curl http://127.0.0.1:5000/login -i -c cookie.txt -b cookie.txt
curl http://127.0.0.1:5000/login -i -c cookie.txt -b cookie.txt ^
  -d "email=user1@email.com" -d "password=password1" ^
  -d "csrf_token=・・・" -L

調査

何が違うのか色々と調べてみたが、原因分からず。
stackoverflowで、requestsのログを出力する方法を見つけて、ログを調査。
https://stackoverflow.com/questions/16337511/log-all-requests-from-the-python-requests-module

import logging
import http.client

logging.basicConfig(level=logging.DEBUG)
httpclient_logger = logging.getLogger("http.client")

def httpclient_logging_patch(level=logging.DEBUG):
    """Enable HTTPConnection debug logging to the logging framework"""

    def httpclient_log(*args):
        httpclient_logger.log(level, " ".join(args))

    # mask the print() built-in in the http.client module to use
    # logging instead
    http.client.print = httpclient_log
    # enable debugging
    http.client.HTTPConnection.debuglevel = 1

httpclient_logging_patch()

原因

最初にrequestsがPOST /loginすると、サーバー側で認証後、レスポンスが返る。
サーバー側がredirectで返しているので、戻り値が302 Foundとなり、
遷移先を取得しようとしてrequestsがGETを発行する。
この際、レスポンスにSet-Cookieが含まれているにも関わらず、
最初のPOSTで指定したcookieをそのまま送ってしまっている。

対応方法1

allow_redirectsをFalseに変更して、
戻り値が302の場合は、cookieを再設定してGETする。

# credentialとsession cookieを指定して、POST /login
cookies = {"session": r.cookies["session"]}
credential = {"email": "user1@email.com", "password": "password1",
    "csrf_token": csrf_token}
r = requests.post("http://127.0.0.1:5000/login", cookies=cookies,
    data=credential, allow_redirects=False)
if r.status_code == 302:
    cookies = {"session": r.cookies["session"]}
    r = requests.get(r.next.url, cookies=cookies)

対応方法2

最初から素直にsessionオブジェクトを使用する。

import requests
from bs4 import BeautifulSoup

session = requests.session()
r = session.get("http://127.0.0.1:5000/login")
doc = BeautifulSoup(r.text, 'html.parser')
csrf_token = doc.find(attrs={'name':'csrf_token'}).get('value')
credential = {"email": "user1@email.com", "password": "password1",
    "csrf_token": csrf_token}
r = session.post("http://127.0.0.1:5000/login",
    data=credential, allow_redirects=True)

おわりに

  • 常識なのかもしれませんが、答えを求めてインターネットを三日間くらい彷徨いました。
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?