requestsでセッションを維持する仕組みがわからず、困ったので検証を行いました。
特にsessionとcookiesの使い方が分からなかったため、その検証を行なっています。
検証方法はDjangoの管理者画面にログイン(POST)し、ログイン状態(セッション)を保持しつつ管理者ページ内の別ページに遷移できるかを確認します。
前提知識
requestsとは
requestsとは、PythonでHTTP通信を行うことができるライブラリのことです。
このライブラリを使用するとWebブラウザを使用せず、プログラム上から直接Webページの情報を取得することができます。
マニュアル: Requests: HTTP for Humans™
pip install requests
sessionとcookieの役割
sessionとcookieの役割について簡単に解説します。
sessionとcookieは、それぞれWebクライアントとWebサーバーが通信を行う際に、使用します。
クライアントがサーバーにログインすると、サーバーはsession_idを発行します。
session_idはクライアントとサーバーの両方に保存され、通信を行なっている間はこのsession_idの送信、受信を繰り返し、通信(セッション)を保持します。
そのおかげで、クライアントがサーバーにログインした状態が維持されます。
cookieというのは、クライアント側でsession_idを保存しておくファイル(もしくデータ領域)のことです。
クライアントがサーバーと通信を行う際は、cookieに記載されている情報(session_idなど)をサーバーに送信します。
検証方法
Django管理者画面のログインページでログイン(POST)を行い、セッションを維持しつつ管理者ページ内の別ページに遷移できるかを確認します。
- ログインページであるhttp://127.0.0.1/admin/login/へアクセス
- ログインページで認証を行い、 認証完了後のページhttp://127.0.0.1/admin/へアクセスする。
- 認証が完了している状態でhttp://127.0.0.1/admin/auth/group/へアクセスする。セッションが切れているとこのページには遷移できない。
1. ログインページ
今回の検証対象となるログインページです。
タブ名がLog in | Django site admin、
URLがhttp://127.0.0.1/admin/login/であることを確認します。
後述するプログラムでは、inputタグのname属性をキーとしてそれぞれ対応する認証情報をrequestsに渡します。
そうすることで、次の認証完了後のページに遷移することができます。
login_data = {
'username': 'admin',
'password': 'hogehoge'
'csrfmiddlewaretoken': 'xxxxxxxxxxxxxxxx'
}
csrfmiddlewaretokenについては、ログインページアクセス時にランダムに生成されるCSRFトークンを渡す必要があります。
上記画像の"value"以降の値をPOSTする際にrequestsへ渡さないと、認証時にエラーとなります。
プログラム上ではBeautifulSoupを利用することでこの値を取得します。
2. 認証完了後のページ
認証が成功するとこの画面に遷移します。
タブ名にSite administrationが含まれており、
URLがhttp://127.0.0.1/admin/であることを確認します。
緑枠のGroupsをクリックすることで、認証完了後に遷移できるページへ移動できます。
なお、セッションが保持されていない場合は、次のページへは遷移せず、先ほどのログインページへ戻ります。
3. 認証完了後に遷移できるページ
セッションが保持されていればこの画面に遷移します。
タブ名にSelect group to changeが含まれており、
URLがhttp://127.0.0.1/admin/auth/group/であることを確認します。
検証環境
上記の環境はDocker-Composeで作成しています。
実際に検証を行なっていみたい方は、ソースコードをGitにアップロードしているのでご利用ください。
git: https://github.com/ruruyuki/django_container_rest
コンテナの使い方
前提
・DockerとDocker Composeがインストール済みであること。
・コマンドラインで作業すること。
作業手順
・上記URL(Git)からダウンロードしたソースを解凍する。
・解凍したディレクトリに「--master 1」などの記述があれば削除し、
名前がdjango_container_restとなるようにする。
・django_container_restディレクトリに、コマンドラインで移動する。
・移動したディレクトリ内で以下コマンドを実行する。
docker-compose up -d --build
・Webブラウザで「http://127.0.0.1:80/admin」にアクセスする。
すると、Djangoの管理画面にアクセスできます。
ユーザー名は「admin」
パスワードは「hogehoge」
でログインすることができます。
検証結果
以下のようにコードを記述すると、先述した条件で通信を行うことができます。
結論としては、cookiesを使用してもセッションを保持できなかったため、sessionクラスを使用してコードを記述しています。
import requests
from bs4 import BeautifulSoup
import re
# 1.ログインページにアクセスする
url_login = "http://127.0.0.1/admin/login/"
session = requests.session()
# ログインページへのアクセス完了
req_before_login = session.get(url_login)
# ログインするための情報を準備する
login_data = {
'username': 'admin',
'password': 'hogehoge'
}
# ログインするためにcsrfトークンが必要となるため情報を取得
bs = BeautifulSoup(req_before_login.text, 'html.parser')
csrf_token = bs.find(
attrs={'name':'csrfmiddlewaretoken'}).get('value')
login_data['csrfmiddlewaretoken'] = csrf_token
# 2. ログインページで認証を行い、管理者ページへ遷移する
req_after_login = session.post(url_login, data=login_data)
# 3. 認証完了後のページで他ページへ遷移を行う
url_group = 'http://127.0.0.1/admin/auth/group/'
req_group = session.get(url_group)
print('--- ログイン情報 ---')
print(login_data)
print('---- 認証ページへのアクセス結果 ---')
print(re.search(r'<title.*', req_before_login.text).group(0))
print(req_before_login.status_code)
print('--- 認証完了ページへのアクセス結果 ---')
print(re.search(r'<title.*', req_after_login.text).group(0))
print(req_after_login.status_code)
print('--- 認証完了ページからgroupページへのアクセス結果 ---')
print(re.search(r'<title.*', req_group.text).group(0))
print(req_group.status_code)
--- ログイン情報 ---
{'username': 'admin', 'password': 'hogehoge', 'csrfmiddlewaretoken': 'Hd4zjEI0G3n0JaJB6GrV6vQFMlQoxtIFEkQbYkJNo5ktR6wlvYf7xdDvrDIZW7Q8'}
--- 認証ページへのアクセス結果 ---
<title>Log in | Django site admin</title>
200
--- 認証完了ページへのアクセス結果 ---
<title>Site administration | Django site admin</title\>
200
--- 認証完了ページからgroupページへのアクセス結果 ---
<title>Select group to change | Django site admin</title\>
200
実行結果の<tiltle\>
で囲まれた部分が各ページのタブ部分の情報です。
確認すると、Log in -> Site administration -> Select group to change の順番で表示されているため、ログインとその後のページ遷移が成功していることがわかります。
ちなみにログインに失敗すると、ページ遷移が行えず Log in -> Log in -> Log in と表示されます。
解説
sessionとcookiesについて
requestsには次のようにコードを書くと、レスポンスヘッダのcookieを取得してくれる機能があります。
response = requests.get(url)
cookie = response.cookies
しかし、本コードではそれを使用していません。
なぜなら、セッションを維持するのに使用するcookieはログインと同時にリクエストヘッダに付与されるからです。
requestsのcookieは、レスポンスヘッダのcookieしか取得できないので、セッション維持には使えません。
では、どうすれば良いかというと、requestsのsessionクラスを使えばこの問題を解決できます。
sessionインスタンスを使用した通信についてはすべて、リクエストヘッダを含むcookieの状態が保持されます。
マニュアル: セッションオブジェクト
sessionは以下のように記述することで、セッション情報を保持しながらHTTP通信を行うことができます。
import requests
session = requests.session()
session.get(url_1)
session.post(url_2)
session.get(url_3)
・・・
sessionインスタンスが保持しているcookieを確認する
下記の例のように、session.cookiesと記述することでsessionインスタンスが保持しているcookieの内容を確認することができます。
セッションが保持されている時は、cookieの内容がどの通信でも同じ内容となります。
# sessionメソッドを使用して通信する
session = requests.session()
session_info_1 = session.cookies
result_1 = session.get(url_1)
session_info_2 = session.cookies
result_2 = session.post(url_2, data=data)
session_info_3 = session.cookies
result_3 = session.get(url_3)
# 結果確認
print(session_info_1)
print(result_1.status_code)
print(session_info_2)
print(result_2.status_code)
print(session_info_2)
print(result_2.status_code)
# 実行結果
# session_info_1
<RequestsCookieJar[<Cookie csrftoken=kjvY43iR7Da7zyztYg6EyfvLzxriLAIsOCNMCNleEokZknFNuVdzCM7NFnQxptdE for 127.0.0.1/>, <Cookie sessionid=l17ftt2yovijmtc3ch4vz6eey48tocpb for 127.0.0.1/>]>
# result_1.status_code
200
# session_info_2
<RequestsCookieJar[<Cookie csrftoken=kjvY43iR7Da7zyztYg6EyfvLzxriLAIsOCNMCNleEokZknFNuVdzCM7NFnQxptdE for 127.0.0.1/>, <Cookie sessionid=l17ftt2yovijmtc3ch4vz6eey48tocpb for 127.0.0.1/>]>
# result_2.status_code
200
# session_info_3
<RequestsCookieJar[<Cookie csrftoken=kjvY43iR7Da7zyztYg6EyfvLzxriLAIsOCNMCNleEokZknFNuVdzCM7NFnQxptdE for 127.0.0.1/>, <Cookie sessionid=l17ftt2yovijmtc3ch4vz6eey48tocpb for 127.0.0.1/>]>
# result_3.status_code
200
cookiesを使用したコード例
こちらはsessionクラスを使わず、cookiesを利用して通信を行なったコードです。
先ほどお話した通り、このコードではログイン後のページ遷移が上手く行えません。
import requests
from bs4 import BeautifulSoup
import re
url_login = 'http://127.0.0.1/admin/login/'
req_before_login = requests.get(url_login)
cookie_before_login = req_before_login.cookies
login_data = {
'username': 'admin',
'password': 'hogehoge'
}
bs = BeautifulSoup(req_before_login.text, 'html.parser')
csrf_token = bs.find(attrs={'name':'csrfmiddlewaretoken'}).get('value')
login_data['csrfmiddlewaretoken'] = csrf_token
req_after_login = requests.post(url_login,
data=login_data,
cookies=cookie_before_login)
cookie_after_login = req_after_login.cookies
url_group = 'http://127.0.0.1/admin/auth/group/'
req_group = requests.get(url_group,
cookies=cookie_after_login)
cookie_group = req_group.cookies
print('--- ログイン情報 ---')
print(login_data)
print('--- 認証ページへのアクセス結果 ---')
print(re.search(r'<title.*', req_before_login.text).group(0))
print(req_before_login.status_code)
print(cookie_before_login)
print('--- login結果 ---')
print(re.search(r'<title.*', req_after_login.text).group(0))
print(req_after_login.status_code)
print(cookie_after_login)
print('--- group画面への遷移結果 ---')
print(re.search(r'<title.*', req_group.text).group(0))
print(req_group.status_code)
print(cookie_group)
--- ログイン情報 ---
{'username': 'admin', 'password': 'hogehoge', 'csrfmiddlewaretoken': 'DJPXPmF54TAr9r27mBJFD5JtoOM8TSfgzkSv5G9X5AlQ7vYslWVOBJ2VB0ZNu6B6'}
--- ログインページへのアクセス結果 ---
<title>Log in | Django site admin</title>
200
<RequestsCookieJar[<Cookie csrftoken=Qr37AviZm64zpc2dTfkEW61jp3epoSSHM26FQPMRnNPYngYySAwNUKkLCfr4Z6ex for 127.0.0.1/>]>
--- login結果 ---
<title>Site administration | Django site admin</title>
200
<RequestsCookieJar[]>
--- group画面への遷移結果 ---
<title>Log in | Django site admin</title>
200
<RequestsCookieJar[<Cookie csrftoken=3q7szjHOk9eFkysm17ShWFrwcJd3qQge7Q41pA0eVwMz1tKJ6tnill0YlFL6lyyM for 127.0.0.1/>]>
結果は Log in -> Site administration -> Log in となっています。
この結果は、ログインには成功しているものの、そのセッションは保持されていないため、管理画面内の別ページに遷移しようとするとログインページに戻されてしまっていることを示しています。
以下のようにPOST実行時のcookieをreq_after_login.cookiesで取得しようとしていますが、実行結果の通り、cookiesの値は空となります。
req_after_login = requests.post(url_login,
data=login_data,
cookies=cookie_before_login)
cookie_after_login = req_after_login.cookies
--- login結果 ---
<title>Site administration | Django site admin</title>
200
<RequestsCookieJar[]>
認証処理が成功するとセッションIDが発行されcookieに保存されますが、このcookieは先述した通りレスポンスヘッダではなく、リクエストヘッダに保持されるため、requestsのcookiesで取得できません。
ゆえにセッションが保持できず、group画面に遷移できず、ログインページに戻っています。
以上からrequestsでセッションを維持したい場合はsessionメソッドを使いましょうという結論になります。