flaskのセッションメモ
まず準備(PowerShell)
python -m venv venv
./venv/Script/Activate
pip install flask
最低限のコードを書く
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
flask run
で起動して、http://127.0.0.1:5000 にアクセスするとCookieがセットされている。
NameはsessionでValueが eyJpZCI6InRlc3QifQ.YrVAGg.l9yOuZmHGsWHNsxbIQHiHuqMXQs
一応バージョンの確認をしておく
flask --version
Python 3.9.5
Flask 2.1.2
Werkzeug 2.1.2
flaskのsessions.pyでset_cookieしているあたりにログを仕込んで何が出ているのか調べてみる
val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore
+ print(dict(session))
+ print(val)
response.set_cookie(
name,
val, # type: ignore
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
)
{'id': 'test'}
と eyJpZCI6InRlc3QifQ.YrVC5g.4-D6YpL2jzODCAEJd3toTfvgTp8
が出力できた
get_signing_serializerで返したクラスがsessionの値を変更しているっぽいので、中身を見てみる
関数はURLSafeTimedSerializerらしいがコード補完的には
itsdangerousというライブラリのserializer.pyのdumpの部分
def dumps(self, obj: _t.Any, salt: _t_opt_str_bytes = None) -> _t_str_bytes:
"""Returns a signed string serialized with the internal
serializer. The return value can be either a byte or unicode
string depending on the format of the internal serializer.
"""
payload = want_bytes(self.dump_payload(obj))
rv = self.make_signer(salt).sign(payload)
+ print(payload)
+ print(rv)
+ print(salt)
if self.is_text_serializer:
return rv.decode("utf-8")
return rv
雑にログを仕込んでみると以下のような結果になる
b'eyJpZCI6InRlc3QifQ'
b'eyJpZCI6InRlc3QifQ.YrVHSw._CNZ3421DGZWaHoltGppQ2y1H84'
None
eyJpZCI6InRlc3QifQ
はbase64でデコードした{"id":"test"}
ですね
YrVHSw
はタイムスタンプをbase64でデコードしたものみたいです。
_CNZ3421DGZWaHoltGppQ2y1H84
はget_signatureの関数内で処理している
flask_loginを入れてみる
pip install flask_login
flask-login-0.6.1が入った。
簡単に実装してみる
from flask import Flask, session
import flask_login
app = Flask(__name__)
app.secret_key = 'sample'
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
@app.route("/")
def hello_world():
session['id'] = 'test'
return "<p>Hello, World!</p>"
class User(flask_login.UserMixin):
def __init__(self, user_id):
self.id = user_id
@login_manager.user_loader
def load_user(user_id):
return User(user_id)
@app.route('/login', methods=['GET', 'POST'])
def login():
# validationチェック
user = User('sample')
flask_login.login_user(user)
# ログインに成功したらmemberページへ飛びます
return "OK"
loginにアクセスすると、入っているcookieの値が変わっている
.eJwlzk0KAjEMQOG7dO2izU_bzGWGpE1QUJAZXYl3t-D-e_A-aY_Dz2vaXsfbL2m_zbQlxelDQ-boAoA0sxTpql4kR2m1VvUscwYZhQSbmiPDdK6Nci4yGBoKZM_sVQ07mHVUUFvBMo24cQmvdegiyoSNZYB1KkaW1sj79ON_c-rjeff0_QHrSTG5.YrV1hA.Eh9FtmBTEtgnYo-ZO39vrQwUQGY
どうやら、一定上の文字列の場合はzipで圧縮しているらしい。
試しにIDに長い文字列をセットしたら同じような挙動になった
@app.route("/")
def hello_world():
session['id'] = 'test'
session['_id'] = '0123456789123456789012345678901234567890123456789012345678901234567890'
return "<p>Hello, World!</p>"
.eJyrVorPTFGyUjIwNDI2MTUzt7CEMwxIZynpKIFNK0ktLlGqBQCreBS9.YrV8Tg.ZVfi8-rmWeJkzqdgGbbWU134iNk
圧縮処理しているのはこの辺
圧縮してみて、サイズが小さくなる場合は利用しているらしい。
なお、圧縮している場合は.で始まる文字列になる
flask_loginを利用している場合はremember_toknの値がcookieに入っている。
flask_loginではセッションのチェック時にremember_tokenの値をまず利用して、取得できなかった場合にsessionの値を利用している
sessionの値はCookieではなくflask側で有効期限が設定されている。
PERMANENT_SESSION_LIFETIMEで設定できる。デフォルトは31日。
PERMANENT_SESSION_LIFETIMEを利用していた値は、バージョンによって違いがあるので注意が必要。
flask 1.0の時はEPOCHという独自の時間が用いられている
現時点の最新バージョンだと、unix timeになっている