セッションの使いかた¶
Django は、匿名のセッションをフルサポートしています。このセッションフレームワークを使えば、任意のデータを、サイトの訪問者ごとに保存し、取得できます。データはサーバー側に保存され、クッキーの送受信によって抽象化します。クッキーには、データそのものではなくセッション ID が書かれています ( クッキーベースのバックエンド を使用しない限り)。
セッションを有効にする¶
セッションは、 ミドルウェア の1つを使って実装されています。
セッションの機能を有効にするには、次のように設定を行います。
MIDDLEWARE
設定を編集し'django.contrib.sessions.middleware.SessionMiddleware'
を含むようにする。django-admin startproject
で作られるデフォルトのsettings.py
はSessionMiddleware
が有効化されています。
セッションを使いたくない場合は、 MIDDLEWARE
内の SessionMiddleware
行と INSTALLED_APPS
内の 'django.contrib.sessions'
を削除しても構いません。これで、オーバーヘッドが少しだけ短縮されます。
セッションエンジンを設定する¶
デフォルトでは、Django はセッションを、(django.contrib.sessions.models.Session
モデルを用いて) データベースに保存します。これは便利ですが、セットアップによってはセッションのデータを他の場所においたほうが高速化できる場合があります。そのため、Django はセッションデータをファイルシステムやキャッシュに保存するように設定できます。
データベースを使ったセッション¶
データベースを使った (database-backed) セッションを使いたい場合には、設定ファイルの INSTALLED_APPS
に 'django.contrib.sessions'
を追加する必要があります。
一度インストールの設定をすれば、manage.py migrate
を実行することで、セッションデータを保存する1つのデータベーステーブルをインストールできます。
キャッシュを使ったセッション¶
よいパフォーマンスを発揮するには、キャッシュを使ったセッションバックエンドを利用した方が良いかもしれません。
Django のキャッシュシステムを使ってセッションデータを保存するには、まず初めに、キャッシュの設定を済ませておく必要があります。詳しくは、 キャッシュのドキュメンテーション を読んでください。
警告
キャッシュを使ったセッションを用いるのは、Memcached または Redis キャッシュバックエンドを使用している場合だけにするべきです。ローカルなメモリキャッシュバックエンドを使用している場合、長時間データをメモリ上に保持しておくのは良い考えではありませんし、全てのデータをファイルやデータベースキャッシュバックエンドを通して送信するよりも、ファイルやデータベースに保存されたセッションから直接送信した方が高速だからです。加えて、ローカルなメモリキャッシュバックエンドは、マルチプロセスに対して安全「ではありません」。したがって、実環境ではあまり良い選択とは言えないでしょう。
設定ファイルの CACHES
で複数のキャッシュを定義している場合、Django はデフォルトのキャッシュを使用します。他のキャッシュを使用するには、SESSION_CACHE_ALIAS
を使用したいキャッシュの名前に変更します。
一度キャッシュが設定されると、データベース ベースのキャッシュ(database-backed cache) か非永続キャッシュ (non-persistent cache) のどちらかを選択する必要があります。
キャッシュ付きデータベースバックエンド(cached_db
)は、ライトスルーキャッシュを使用します。セッションの書き込みは、データベースとキャッシュの両方に、順番に適用されます。キャッシュへの書き込みが失敗した場合、例外処理が行われ、sessions logger を通じて記録されます。これにより、片方だけで成功した書き込み操作が失敗することを防ぎます。
キャッシュへの書き込み時に発生する例外の処理およびログ記録機能が追加されました。
セッションの読み取りは、まずキャッシュを使用し、データがキャッシュから削除されている場合にはデータベースを使用します。このバックエンドを使用するには、SESSION_ENGINE
を "django.contrib.sessions.backends.cached_db"
に設定し、using database-backed sessions の構成手順に従ってください。
キャッシュバックエンド (cache
) はセッションデータをキャッシュにのみ保存します。これはデータベースの永続化を避けることができるので高速ですが、キャッシュデータが削除されたときのことを考慮しなければなりません。キャッシュが一杯になったりキャッシュサーバーが再起動したりしたときにキャッシュが削除される可能性があり、ユーザーのログアウトを含めてセッションデータが失われることになります。このバックエンドを使うには、 SESSION_ENGINE
を "django.contrib.sessions.backends.cache"
に設定してください。
キャッシュバックエンドを永続化するには、Redis などの永続キャッシュを適切な設定で使用します。しかし、キャッシュが十分に永続化できるように確実に設定されていない限り、キャッシュデータベースバックエンドを選びましょう。これにより、運用時に信頼性の低いデータストレージによって引き起こされるエッジケースを避けることができます。
ファイルを使ったセッション¶
ファイルを使ったセッションを使用するには、SESSION_ENGINE
を "django.contrib.sessions.backends.file"
に設定します。
Django がセッションファイルを保存する場所を設定したい場合には、SESSION_FILE_PATH
を設定します (出力先のデフォルト値は、tempfile.gettempdir()
、普通は /tmp
になっています)。ウェブサーバが設定した場所の読み書きの権限を持っていることを確認しておいてください。
ビューでセッションを使う¶
SessionMiddleware
を有効にすれば、それぞれの HttpRequest
オブジェクト(Django のビュー関数に渡される最初の引数)は、辞書ライクなオブジェクトの session
属性を持つようになります。
この request.session
には、ビューの好きな場所で、何度でも、読み書きを行うことができます。
- class backends.base.SessionBase¶
このオブジェクトは、すべてのセッションオブジェクトのベースクラスとなっているので、以下に挙げるような基本的なディクショナリのメソッドを持っています。
- __getitem__(key)¶
例:
fav_color = request.session['fav_color']
- __setitem__(key, value)¶
例:
request.session['fav_color'] = 'blue'
- __delitem__(key)¶
例:
del request.session['fav_color']
与えられたkey
がセッション内にない場合には、KeyError
例外を起こします。
- __contains__(key)¶
例:
'fav_color' in request.session
- get(key, default=None)¶
- aget(key, default=None)¶
非同期バージョン:
aget()
例:
fav_color = request.session.get('fav_color', 'red')
Changed in Django 5.1:aget()
関数が追加されました。
- aset(key, value)¶
- New in Django 5.1.
例:
await request.session.aset('fav_color', 'red')
- update(dict)¶
- aupdate(dict)¶
非同期バージョン:
aupdate()
例:
request.session.update({'fav_color': 'red'})
Changed in Django 5.1:aupdate()
関数が追加されました。
- pop(key, default=__not_given)¶
- apop(key, default=__not_given)¶
非同期バージョン:
apop()
例:
fav_color = request.session.pop('fav_color', 'blue')
Changed in Django 5.1:apop()
関数が追加されました。
- keys()¶
- akeys()¶
非同期バージョン:
akeys()
Changed in Django 5.1:akeys()
関数が追加されました。
- values()¶
- avalues()¶
非同期バージョン:
avalues()
Changed in Django 5.1:avalues()
関数が追加されました。
- has_key(key)¶
- ahas_key(key)¶
非同期バージョン:
ahas_key()
Changed in Django 5.1:ahas_key()
関数が追加されました。
- items()¶
- aitems()¶
非同期バージョン:
aitems()
Changed in Django 5.1:aitems()
関数が追加されました。
- setdefault()¶
- asetdefault()¶
非同期バージョン:
asetdefault()
Changed in Django 5.1:asetdefault()
関数が追加されました。
- clear()¶
また、次のようなメソッドも利用できます。
- flush()¶
- aflush()¶
非同期バージョン:
aflush()
セッションから現在のセッションデータを削除し、セッションクッキーを削除します。このメソッドは、前回のセッションデータがユーザのブラウザから再びアクセスされないようにするためなどに使います (たとえば、
django.contrib.auth.logout()
がこの関数を呼びます)。Changed in Django 5.1:aflush()
関数が追加されました。
- set_test_cookie()¶
- aset_test_cookie()¶
非同期バージョン:
aset_test_cookie()
ユーザのブラウザがクッキーをサポートしているかどうか判定するために、テスト用のクッキーをセットします。クッキーの動作原理により、ユーザが次のページへのリクエストを行わないとこのテストは行えません。詳しい情報については、下の Setting test cookies を読んでください。
Changed in Django 5.1:aset_test_cookie()
関数が追加されました。
- test_cookie_worked()¶
- atest_cookie_worked()¶
非同期バージョン:
atest_cookie_worked()
ユーザーのブラウザがテストクッキーを受け入れたかどうかに応じて、
True
またはFalse
を返します。クッキーの仕組み上、別のページリクエストで事前にset_test_cookie()
またはaset_test_cookie()
を呼び出す必要があります。詳細は、以下の Setting test cookies を参照してください。Changed in Django 5.1:atest_cookie_worked()
関数が追加されました。
- delete_test_cookie()¶
- adelete_test_cookie()¶
非同期バージョン:
adelete_test_cookie()
テストクッキーを削除します。クッキーをきれいにしておくために使ってください。
Changed in Django 5.1:adelete_test_cookie()
関数が追加されました。
- get_session_cookie_age()¶
SESSION_COOKIE_AGE
の設定値を返します。これはカスタム セッション バックエンドで上書きできます。
- set_expiry(value)¶
- aset_expiry(value)¶
非同期バージョン:
aset_expiry()
セッションの有効期限を設定します。以下に挙げるようなさまざまな値を与えることができます。
もし
value
が整数なら、与えられた秒数だけ活動がなかった時にセッションを破棄します。たとえば、request.session.set_expiry(300)
と呼び出せば、5分後にセッションを破棄するように設定できます。もし
value
がdatetime
またはtimedelta
オブジェクトであれば、セッションはその日時で終了します。もし
value
が0
ならば、ユーザのセッションクッキーは、ユーザがウェブブラウザを閉じた時に破棄されます。もし
value
がNone
ならば、セッションはグローバルなセッション有効期限ポリシーにしたがって扱われます。
セッションの読み込みを行っても、有効期限は延長されません。セッションの有効期限は、セッションが最後に 修正された 時点を基に計算されます。
Changed in Django 5.1:aset_expiry()
関数が追加されました。
- get_expiry_age()¶
- aget_expiry_age()¶
非同期バージョン:
aget_expiry_age()
このセッションの有効期限までの残りの秒数を返します。カスタムの有効期限を持たない (または、ブラウザを閉じた時とセットした) セッションの場合、この値は
SESSION_COOKIE_AGE
と等しいです。この関数は 2 つの省略可能なキーワード引数を取ることができます。
modification
: セッションを最後に修正した時刻をdatetime
オブジェクトとして与える。デフォルトは現在の時刻。expiry
: セッションの有効期限情報を指定します。datetime
オブジェクト、int
(秒単位)、またはNone
を使用できます。デフォルトでは、set_expiry()
/aset_expiry()
によってセッションに保存された値(存在する場合)か、None
になります。
注釈
このメソッドは、セッションを保存する際にセッションの有効期限を秒単位で決定するためにセッションバックエンドで使用されます。このようなコンテキスト以外での使用は想定していません。
特に、正しい
modification
値があり、 かつexpiry
がdatetime
オブジェクトとしてセットされている場合にのみ、セッションの残り寿命を決定することが 可能 ですが、modification
値を持っている場合、有効期限を手動で計算する方がより直接的です:expires_at = modification + timedelta(seconds=settings.SESSION_COOKIE_AGE)
Changed in Django 5.1:aget_expiry_age()
関数が追加されました。
- get_expiry_date()¶
- aget_expiry_date()¶
非同期バージョン:
aget_expiry_date()
このセッションが破棄される日付を返します。カスタムの有効期限を持たない (または、ブラウザを閉じた時とセットした) セッションに対しては、現在時刻から
SESSION_COOKIE_AGE
の秒数までの日付に等しいです。この関数は、
get_expiry_age()
と同じキーワード引数を取ることができ、使用の際の注意点も同じです。Changed in Django 5.1:aget_expiry_date()
関数が追加されました。
- get_expire_at_browser_close()¶
- aget_expire_at_browser_close()¶
非同期バージョン:
aget_expire_at_browser_close()
ユーザのセッションクッキーがユーザのブラウザが閉じた時に破棄されるかどうかに応じて、
True
またはFalse
を返します。Changed in Django 5.1:aget_expire_at_browser_close()
関数が追加されました。
- clear_expired()¶
- aclear_expired()¶
非同期バージョン:
aclear_expired()
保存されているセッションから、有効期限が切れているものを削除します。このクラスメソッドは、
clearsessions
から呼び出されます。Changed in Django 5.1:aclear_expired()
関数が追加されました。
- cycle_key()¶
- acycle_key()¶
非同期バージョン:
acycle_key()
現在のセッションデータを保持したまま、新しいセッションキーを作成します。
django.contrib.auth.login()
は、このメソッドを呼び出すことで、過去のセッションを継続できるようにしています。Changed in Django 5.1:acycle_key()
関数が追加されました。
セッションのシリアライズ¶
デフォルトでは、Django はセッションデータを JSON を用いてシリアライズします。設定ファイルの SESSION_SERIALIZER
に設定すれば、セッションをシリアライズするフォーマットをカスカムできます。 自作のシリアライザを書く に書いた注意書きの通り、JSON によるシリアライズを強く推奨します。 クッキーバックエンドを利用している場合は特に です。
たとえば、セッションデータをシリアライズするために pickle
を使用していた場合の、こんな攻撃のシナリオを考えてみましょう。あなたは 署名したクッキーセッションバックエンド を使用していて、SECRET_KEY
(または SECRET_KEY_FALLBACKS
の任意のキー) が攻撃者に知られてしまっていたとします (もちろん、これは Django にキーが流出する脆弱性が内在しているというわけではありません)。この時、攻撃者は、セッションに任意の文字列を挿入することができてしまい、このセッションを unpickle すると、サーバ上で任意のコードが実行されてしまいます。このような攻撃は、インターネット上のどこからでも簡単にできてしまいます。クッキーセッションストレージは、クッキーに保存されているデータが勝手に書き換えられないようにクッキーに署名をしていますが、 SECRET_KEY
が流出するだけで、リモートからコードを実行されてしまう脆弱性が生まれてしまうのです。
バンドルされているシリアライザ¶
- class serializers.JSONSerializer¶
django.core.signing
の JSON シリアライザのラッパーです。基本的なデータ型だけがシリアライズ可能です。また、JSON は文字列のキーしかサポートしていないので、
request.session
に文字列以外のキーを使用してしまうと、次のように期待通りに動いてくれません。>>> # initial assignment >>> request.session[0] = "bar" >>> # subsequent requests following serialization & deserialization >>> # of session data >>> request.session[0] # KeyError >>> request.session["0"] 'bar'
同様に、
'\xd9'
のような UTF-8 以外のバイトなど、JSONでエンコードできないデータは保存できません(これはUnicodeDecodeError
を発生させます)。JSON によるシリアライズの制限について詳しくは、自作のシリアライザを書く セクションを読んでください。
自作のシリアライザを書く¶
JSONSerializer
では、任意の Python データ型を扱うことができません。よくあることですが、利便性とセキュリティはトレード・オフの関係にあります。JSON を使ったセッション内に datetime
や Decimal
といった、より高度なデータ型を保存したければ、カスタムのシリアライザを書く (か、request.session
に保存する前に、あらかじめその値を JSON でシリアライズ可能なオブジェクトに変換しておく) 必要があります。これらの値をシリアライズするのは全然難しくありません (DjangoJSONEncoder
が役に立つかも知れません) が、シリアライズしたオブジェクトと全く同じデータを再取得できる信頼性の高いデコーダを書く方は、もう少し難しい仕事になります。たとえば、本当は文字列であるにも関わらず、たまたま datetime
を表現するのと全く同じフォーマットの文字列に出会うという可能性があります。
シリアライザのクラスは、必ず次の2つのメソッドを実装しなければなりません。セッションデータのディクショナリをシリアライズする dumps(self, obj)
と、それをデシリアライズする loads(self, data)
の2つです。
セッションオブジェクトのガイドライン¶
Python の普通の文字列を、
request.session
におけるディクショナリのキーとして使うこと。これは慣習のためというよりも、確実で高速にするためのルールです。セッションのディクショナリのキーで、アンダースコアで始まるキーは、Django が内部で使用するために予約されているものと考えること。
request.session
を新しいオブジェクトで上書きせず、その属性にアクセスしたり、値をセットしたりしないこと。Python のディクショナリのように扱うこと。
例¶
次の簡単なビューは、ユーザがコメントを投稿した後で、has_commented
変数を True
に設定します。こうすることで、同じユーザが2回以上コメントできないようにすることができます。
def post_comment(request, new_comment):
if request.session.get("has_commented", False):
return HttpResponse("You've already commented.")
c = comments.Comment(comment=new_comment)
c.save()
request.session["has_commented"] = True
return HttpResponse("Thanks for your comment!")
次の次の簡単なビューでは、サイトの「メンバー (member)」にログインする手続きを行います。
def login(request):
m = Member.objects.get(username=request.POST["username"])
if m.check_password(request.POST["password"]):
request.session["member_id"] = m.id
return HttpResponse("You're logged in.")
else:
return HttpResponse("Your username and password didn't match.")
そして、次の例では、上の login()
に対応するメンバーのログアウト処理を行います。
def logout(request):
try:
del request.session["member_id"]
except KeyError:
pass
return HttpResponse("You're logged out.")
ふつう実際に使われる django.contrib.auth.logout()
関数では、データの意図しない漏洩を防ぐために、この例より少し複雑な処理、request.session
の flush()
メソッドを呼び出すなどの処理を行っています。ここで挙げた例は、単にセッションオブジェクトの振る舞いをデモンストレーションすることが目的なので、完全ではない logout()
を実装しました。
ビューの外でセッションを使う¶
注釈
このセクションの例では、SessionStore
オブジェクトを直接 django.contrib.sessions.backends.db
バックエンドからインポートしています。しかし、実際に自分でコートを書く場合には、以下のようにして、SESSION_ENGINE
が指すセッションエンジンから SessionStore
をインポートすることを考えるべきです。
>>> from importlib import import_module
>>> from django.conf import settings
>>> SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
次のようにして、ビューの外からセッションデータを操作する API が利用可能です。
>>> from django.contrib.sessions.backends.db import SessionStore
>>> s = SessionStore()
>>> # stored as seconds since epoch since datetimes are not serializable in JSON.
>>> s["last_login"] = 1376587691
>>> s.create()
>>> s.session_key
'2b1189a188b44ad18c35e113ac6ceead'
>>> s = SessionStore(session_key="2b1189a188b44ad18c35e113ac6ceead")
>>> s["last_login"]
1376587691
SessionStore.create()
は新しいセッション(つまりセッションストアから読み込まれていない、session_key=None
のセッション)を作成するためのものです。save()
は既存のセッションを保存するために設計されています(つまり、セッションストアから読み込まれたもの)。新しいセッションに対して save()
を呼び出すこともできますが、既存のセッションと session_key
が衝突する可能性があります。 create()
は save()
を呼び出し、使用されていない session_key
が生成されるまでループします。
django.contrib.sessions.backends.db
バックエンドを使用している場合、各セッションは単なる Django のモデルになっていて、Session
モデルは django/contrib/sessions/models.py で定義されています。普通のモデルにすぎないので、普通の Django データベース API からセッションにアクセスできます。
>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk="2b1189a188b44ad18c35e113ac6ceead")
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)
セッションのディクショナリを取得するには、get_decoded()
を呼ばなければならないことに注意してください。これが必要なのは、ディクショナリがエンコードされたフォーマットで保存されているためです。
>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
>>> s.get_decoded()
{'user_id': 42}
セッションが保存されるタイミング¶
デフォルトでは、Django がセッションデータベースへデータを保存するのは、セッションが修正された時だけ、つまり、ディクショナリ直下の値が代入または削除された時だけです。
# Session is modified.
request.session["foo"] = "bar"
# Session is modified.
del request.session["foo"]
# Session is modified.
request.session["foo"] = {}
# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session["foo"]["bar"] = "baz"
上の例の場合、セッションオブジェクトに修正したことを明示的に伝えるためには、modified
属性を設定すれば良いです。
request.session.modified = True
このデフォルトの動作を変更するには、設定ファイルの SESSION_SAVE_EVERY_REQUEST
を True
に設定します。True
に設定すると、Django は1リクエストごとに、セッションをデータベースに保存してくれるようになります。
セッションクッキーが送信されるのは、セッションの作成及び修正時のみであることに注意してください。SESSION_SAVE_EVERY_REQUEST
を True
に設定すると、各リクエストごとにセッションクッキーが送信されるようになります。
同時に、セッションクッキーの expires
部分も、セッションクッキーが送信されるごとに更新されます。
レスポンスステータスコードが 500 の時には、セッションは保存されません。
ブラウザ起動中のみ有効なセッション vs. 永続的なセッション¶
SESSION_EXPIRE_AT_BROWSER_CLOSE
設定を使うと、セッションフレームワークが、ブラウザ起動中のみ有効なセッションと永続的なセッションのどちらを使うか設定できます。
デフォルトでは、SESSION_EXPIRE_AT_BROWSER_CLOSE
は False
にセットされていて、セッションクッキーは SESSION_COOKIE_AGE
の期間ユーザのブラウザに保持されます。これにより、ユーザはブラウザを開くたびにログインし直さずに済みます。
SESSION_EXPIRE_AT_BROWSER_CLOSE
を True
に設定すると、Django はブラウザ起動中のみ有効なクッキーを使用します。このクッキーは、ブラウザを閉じた瞬間に破棄されます。ユーザがブラウザを開くたびにログインするようにしたい場合には、この設定を利用してください。
この設定はグローバルなデフォルトですが、1セッションごとのレベルで設定を上書きできます。その場合には、上の using sessions in views で書いたようにして、request.session
の set_expiry()
メソッドを呼び出してください。
注釈
一部のブラウザ (たとえば Chrome) では、ブラウザを閉じて再度開いてもセッションを継続できるようにする設定が提供されています。場合によっては、この設定が SESSION_EXPIRE_AT_BROWSER_CLOSE
設定を妨げて、ブラウザが閉じてもセションが破棄されないことがあります。SESSION_EXPIRE_AT_BROWSER_CLOSE
を有効にした Django アプリケーションのテストをする時には、この点に注意してください。
セッションストアのクリア¶
ユーザがウェブサイトで新しいセッションを作成した時、セッションデータはセッションストアに溜め込まれます。データベースのバックエンドを使っている場合には、django_session
データベーステーブルが増大し、ファイルバックエンドを使っている場合には、一時ディレクトリのファイル数が増えてゆくでしょう。
この問題を理解するには、データベースバックエンドで起きていることを考えてみてください。ユーザがログインすると、Django は django_session
データベースのテーブルに1行を追加します。Django はセッションのデータが変更されるたびに、この行を更新します。ユーザが手動でログアウトすると、Django はこの行を削除します。しかし、ユーザがログアウト しなかった 場合には、この行は決して削除されません。同様のプロセスが、ファイルバックエンドの場合にも発生します。
Django は、有効期限の切れたセッションを自動的に削除する機能を提供 しません 。したがって、定期的に有効期限の切れたセッションを削除する仕事は、開発者の手に委ねられています。Django はこの目的のために、クリーンナップ用の管理コマンド clearsessions
を用意しています。たとえば、cron の毎日のジョブに追加するなどの方法で、定期的にこのコマンドを実行することが推奨されています。
キャッシュバックエンドの場合はこの問題が発生しないことに注意してください。キャッシュの場合、不要なデータは自動的に削除されるようになっているからです。しかし、クッキーバックエンドの場合はそうではありません。セッションデータはユーザのブラウザに保存されるからです。
設定¶
以下の Django の設定 を使うと、セッションの動作をコントロールできます。
セッションのセキュリティ¶
サイトのサブドメインからは、クライアントに対して、ドメイン全体に対するクッキーを設定できます。信頼できるユーザに管理されていないサブドメインから送られたクッキーを許可している場合、この方法でセッションの固定化 (session fixation) 攻撃が可能になってしまいます。
たとえば、攻撃者が good.example.com
にログインして、攻撃者のアカウントに対する有効なセッションを取得したとしましょう。この攻撃者が bad.example.com
を自由に使えた場合、サブドメインからは *.example.com
に対してクッキーを設定できるので、先ほど取得したセッションを利用して、攻撃者のセッションキーをターゲットの利用者に送信できてしまいます。次にこの利用者が good.example.com
を訪問すると、この利用者は攻撃者のアカウントにログインすることになり、そのことに気付かずに大切な個人のデータ (たとえば、クレジットカード情報など) を攻撃者のアカウントに入力してしまう恐れがあります。
もう一つ考えられる攻撃は、good.example.com
というサイトが SESSION_COOKIE_DOMAIN
を "example.com"
に設定していた場合に、そのサイトのセッションクッキーが bad.example.com
に送信されてしまう、というものです。
技術的な詳細¶
セッション辞書は、
JSONSerializer
を使用する場合、任意のjson
シリアライズ可能な値を受け入れます。セッションデータは、データベースの
django_session
という名前のテーブルに保存されます。Django は必要なときにだけクッキーを送信します。新しくセッションデータを設定しなければ、Django はセッションクッキーを送信しません。
SessionStore
オブジェクト¶
Django が内部でセッションを操作する時は、対応するセッションエンジンの session store オブジェクトを使用します。慣習により、session store オブジェクトは SessionStore
と名付けられていて、SESSION_ENGINE
で指定したモジュール内に置かれています。
Django で利用可能なすべての SessionStore
のサブクラスは、以下のデータ操作メソッドを実装しています。
exists()
create()
save()
delete()
load()
これらのメソッドに対する非同期インターフェースは、sync_to_async()
でラップすることで提供されます。また、非同期ネイティブな実装が利用可能な場合は、以下の関数をダイレクトに使えます:
aexists()
acreate()
asave()
adelete()
aload()
独自のセッションエンジンを作ったり、既存のものをカスタマイズするには、SessionBase
か既存の SessionStore
クラスを継承した新しいクラスを作る必要があるでしょう。
セッションエンジンを拡張することもできますが、データベースをバックエンドとするセッションエンジンを拡張するには通常、追加の労力が必要です(詳しくは次のセクションを参照してください)。
aexists()
, acreate()
, asave()
, adelete()
, aload()
, aclear_expired()
メソッドが追加されました。
データベースを使ったセッションを拡張する¶
Django に含まれるデータベースを使ったセッションエンジン (具体的に言えば、db
と cached_db
) をカスタマイズするには、AbstractBaseSession
とともに SessionStore
クラスを継承する必要があるかもしれません。
AbstractBaseSession
と BaseSessionManager
は、INSTALLED_APPS
に django.contrib.sessions
を追加しなくても、django.contrib.sessions.base_session
からインポートできます。
- class base_session.AbstractBaseSession¶
ベースとなる抽象的なセッションのモデルです。
- session_key¶
プライマリーキーです。フィールド自体は40文字までの文字列を格納できますが、現在の実装では、32文字の文字列 (数字と小文字のASCII文字のランダムな列) を生成します。
- session_data¶
エンコード・シリアライズされた、セッションディクショナリーを含む文字列です。
- expire_date¶
セッションの有効期限を表す datetime オブジェクトです。
有効期限が切れたセッションをユーザが利用することはできませんが、
clearsessions
管理コマンドを実行するまでは、ふつうはデータベースに保存されています。
- classmethod get_session_store_class()¶
セッションモデルで使用するセッションストアクラスを返します。
- get_decoded()¶
デコードしたセッションデータを返します。
デコードは、セッションストアのクラスで実行されます。
BaseSessionManager
のサブクラスを作ることで、モデルマネージャをカスタマイズすることもできます。
- class base_session.BaseSessionManager¶
- encode(session_dict)¶
与えられたセッションディクショナリを、シリアライズ・エンコードした文字列として返します。
エンコードは、モデルクラスに関連付けられたセッションストアのクラスで実行されます。
- save(session_key, session_dict, expire_date)¶
与えられたセッションキーに対するセッションデータを保存します。データが空の場合には、セッションを削除します。
SessionStore
クラスのカスタマイズは、以下に挙げるメソッドやプロパティをオーバーライドすることで行えます。
- class backends.db.SessionStore¶
データベースを使ったセッションストアを実装しています。
- classmethod get_model_class()¶
カスタマイズしたセッションモデルを返す必要がある場合には、このメソッドをオーバーライドします。
- create_model_instance(data)¶
現在のセッションの状態を表す、セッションモデルオブジェクトの新しいインスタンスを返します。
このメソッドをオーバーライドすると、セッションモデルのデータをデータベースへ保存する前に修正できます。
- class backends.cached_db.SessionStore¶
キャッシュデータベースを使ったセッションストアを実装しています。
- cache_key_prefix¶
キャッシュのキー文字列を作るためにセッションキーに追加するプリフィックスです。
カスタマイズ例¶
以下の例は、アカウントIDを格納するための追加のデータベース列を含むカスタムのデータベースバックのセッションエンジンを示しており、アカウントのすべてのアクティブセッションに対してデータベースをクエリするオプションを提供します:
from django.contrib.sessions.backends.db import SessionStore as DBStore
from django.contrib.sessions.base_session import AbstractBaseSession
from django.db import models
class CustomSession(AbstractBaseSession):
account_id = models.IntegerField(null=True, db_index=True)
@classmethod
def get_session_store_class(cls):
return SessionStore
class SessionStore(DBStore):
@classmethod
def get_model_class(cls):
return CustomSession
def create_model_instance(self, data):
obj = super().create_model_instance(data)
try:
account_id = int(data.get("_auth_user_id"))
except (ValueError, TypeError):
account_id = None
obj.account_id = account_id
return obj
Django の組み込みの cached_db
セッションストアから、cached_db
をベースにしたカスタムのものにマイグレーションする場合は、名前空間の衝突を防ぐためにキャッシュキーのプレフィックスをオーバーライドするべきです:
class SessionStore(CachedDBStore):
cache_key_prefix = "mysessions.custom_cached_db_backend"
# ...
URL 内のセッション ID¶
Django のセッションフレームワークは、完全かつ単独でクッキーをベースにしています。PHP のように最後の手段として URL にセッション ID を入れるようなことはありません。これは、意図的な設計上の決定です。そのような振る舞いは、URL を醜くしてしまうだけではなく、"Referer" ヘッダを介したセッション ID の盗難に対してサイトを脆弱にしてしまいます。