セッションの使いかた

Django は、匿名のセッションをフルサポートしています。このセッションフレームワークを使えば、任意のデータを、サイトの訪問者ごとに保存し、取得できます。データはサーバー側に保存され、クッキーの送受信によって抽象化します。クッキーには、データそのものではなくセッション ID が書かれています ( クッキーベースのバックエンド を使用しない限り)。

セッションを有効にする

セッションは、 ミドルウェア の1つを使って実装されています。

セッションの機能を有効にするには、次のように設定を行います。

  • MIDDLEWARE 設定を編集し 'django.contrib.sessions.middleware.SessionMiddleware' を含むようにする。 django-admin startproject で作られるデフォルトの settings.pySessionMiddleware が有効化されています。

セッションを使いたくない場合は、 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) のどちらかを選択する必要があります。

The cached database backend (cached_db) uses a write-through cache -- session writes are applied to both the database and cache, in that order. If writing to the cache fails, the exception is handled and logged via the sessions logger, to avoid failing an otherwise successful write operation.

Changed in Django 5.1:

Handling and logging of exceptions when writing to the cache was added.

Session reads use the cache, or the database if the data has been evicted from the cache. To use this backend, set SESSION_ENGINE to "django.contrib.sessions.backends.cached_db", and follow the configuration instructions for the 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)

Asynchronous version: aget()

例: fav_color = request.session.get('fav_color', 'red')

Changed in Django 5.1:

aget() function was added.

aset(key, value)
New in Django 5.1.

Example: await request.session.aset('fav_color', 'red')

update(dict)
aupdate(dict)

Asynchronous version: aupdate()

Example: request.session.update({'fav_color': 'red'})

Changed in Django 5.1:

aupdate() function was added.

pop(key, default=__not_given)
apop(key, default=__not_given)

Asynchronous version: apop()

例: fav_color = request.session.pop('fav_color', 'blue')

Changed in Django 5.1:

apop() function was added.

keys()
akeys()

Asynchronous version: akeys()

Changed in Django 5.1:

akeys() function was added.

values()
avalues()

Asynchronous version: avalues()

Changed in Django 5.1:

avalues() function was added.

has_key(key)
ahas_key(key)

Asynchronous version: ahas_key()

Changed in Django 5.1:

ahas_key() function was added.

items()
aitems()

Asynchronous version: aitems()

Changed in Django 5.1:

aitems() function was added.

setdefault()
asetdefault()

Asynchronous version: asetdefault()

Changed in Django 5.1:

asetdefault() function was added.

clear()

また、次のようなメソッドも利用できます。

flush()
aflush()

Asynchronous version: aflush()

セッションから現在のセッションデータを削除し、セッションクッキーを削除します。このメソッドは、前回のセッションデータがユーザのブラウザから再びアクセスされないようにするためなどに使います (たとえば、django.contrib.auth.logout() がこの関数を呼びます)。

Changed in Django 5.1:

aflush() function was added.

Asynchronous version: aset_test_cookie()

ユーザのブラウザがクッキーをサポートしているかどうか判定するために、テスト用のクッキーをセットします。クッキーの動作原理により、ユーザが次のページへのリクエストを行わないとこのテストは行えません。詳しい情報については、下の Setting test cookies を読んでください。

Changed in Django 5.1:

aset_test_cookie() function was added.

Asynchronous version: atest_cookie_worked()

Returns either True or False, depending on whether the user's browser accepted the test cookie. Due to the way cookies work, you'll have to call set_test_cookie() or aset_test_cookie() on a previous, separate page request. See Setting test cookies below for more information.

Changed in Django 5.1:

atest_cookie_worked() function was added.

Asynchronous version: adelete_test_cookie()

テストクッキーを削除します。クッキーをきれいにしておくために使ってください。

Changed in Django 5.1:

adelete_test_cookie() function was added.

SESSION_COOKIE_AGE の設定値を返します。これはカスタム セッション バックエンドで上書きできます。

set_expiry(value)
aset_expiry(value)

Asynchronous version: aset_expiry()

セッションの有効期限を設定します。以下に挙げるようなさまざまな値を与えることができます。

  • もし value が整数なら、与えられた秒数だけ活動がなかった時にセッションを破棄します。たとえば、request.session.set_expiry(300) と呼び出せば、5分後にセッションを破棄するように設定できます。
  • もし valuedatetime または timedelta オブジェクトであれば、セッションはその日時で終了します。
  • もし value0 ならば、ユーザのセッションクッキーは、ユーザがウェブブラウザを閉じた時に破棄されます。
  • もし valueNone ならば、セッションはグローバルなセッション有効期限ポリシーにしたがって扱われます。

セッションの読み込みを行っても、有効期限は延長されません。セッションの有効期限は、セッションが最後に 修正された 時点を基に計算されます。

Changed in Django 5.1:

aset_expiry() function was added.

get_expiry_age()
aget_expiry_age()

Asynchronous version: aget_expiry_age()

このセッションの有効期限までの残りの秒数を返します。カスタムの有効期限を持たない (または、ブラウザを閉じた時とセットした) セッションの場合、この値は SESSION_COOKIE_AGE と等しいです。

この関数は 2 つの省略可能なキーワード引数を取ることができます。

  • modification: セッションを最後に修正した時刻を datetime オブジェクトとして与える。デフォルトは現在の時刻。
  • expiry: expiry information for the session, as a datetime object, an int (in seconds), or None. Defaults to the value stored in the session by set_expiry()/aset_expiry(), if there is one, or None.

注釈

このメソッドは、セッションを保存する際にセッションの有効期限を秒単位で決定するためにセッションバックエンドで使用されます。このようなコンテキスト以外での使用は想定していません。

特に、正しい modification 値があり、 かつ expirydatetime オブジェクトとしてセットされている場合にのみ、セッションの残り寿命を決定することが 可能 ですが、 modification 値を持っている場合、有効期限を手動で計算する方がより直接的です:

expires_at = modification + timedelta(seconds=settings.SESSION_COOKIE_AGE)
Changed in Django 5.1:

aget_expiry_age() function was added.

get_expiry_date()
aget_expiry_date()

Asynchronous version: aget_expiry_date()

このセッションが破棄される日付を返します。カスタムの有効期限を持たない (または、ブラウザを閉じた時とセットした) セッションに対しては、現在時刻から SESSION_COOKIE_AGE の秒数までの日付に等しいです。

この関数は、get_expiry_age() と同じキーワード引数を取ることができ、使用の際の注意点も同じです。

Changed in Django 5.1:

aget_expiry_date() function was added.

get_expire_at_browser_close()
aget_expire_at_browser_close()

Asynchronous version: aget_expire_at_browser_close()

ユーザのセッションクッキーがユーザのブラウザが閉じた時に破棄されるかどうかに応じて、True または False を返します。

Changed in Django 5.1:

aget_expire_at_browser_close() function was added.

clear_expired()
aclear_expired()

Asynchronous version: aclear_expired()

保存されているセッションから、有効期限が切れているものを削除します。このクラスメソッドは、clearsessions から呼び出されます。

Changed in Django 5.1:

aclear_expired() function was added.

cycle_key()
acycle_key()

Asynchronous version: acycle_key()

現在のセッションデータを保持したまま、新しいセッションキーを作成します。django.contrib.auth.login() は、このメソッドを呼び出すことで、過去のセッションを継続できるようにしています。

Changed in Django 5.1:

acycle_key() function was added.

セッションのシリアライズ

デフォルトでは、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 を使ったセッション内に datetimeDecimal といった、より高度なデータ型を保存したければ、カスタムのシリアライザを書く (か、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.sessionflush() メソッドを呼び出すなどの処理を行っています。ここで挙げた例は、単にセッションオブジェクトの振る舞いをデモンストレーションすることが目的なので、完全ではない logout() を実装しました。

テストクッキーを設定する

利便性のため、Django はユーザのブラウザがクッキーを受け入れることができるかどうかをテストするための方法を提供しています。テストをするには、一度ビューの中で request.sessionset_test_cookie() メソッドを呼び出してから、同じビューではなく、次に呼び出されるビューの中で、 test_cookie_worked() メソッドを呼び出します。

このように set_test_cookie()test_cookie_worked() を分離しなければならないのはちょっと気持ち悪いですが、クッキーの動作原理により、こうするより仕方がないのです。一度ブラウザにクッキーを設定しても、そのクッキーがブラウザに保存されたかどうかを確認するには、ブラウザがもう一度リクエストを行わなければならないからです。

テストクッキーがちゃんと機能していることを確認できたら、delete_test_cookie() を使ってセッションをきれいにしておくことにしましょう。

以下に、テストクッキーの典型的な使用例を挙げます。

from django.http import HttpResponse
from django.shortcuts import render


def login(request):
    if request.method == "POST":
        if request.session.test_cookie_worked():
            request.session.delete_test_cookie()
            return HttpResponse("You're logged in.")
        else:
            return HttpResponse("Please enable cookies and try again.")
    request.session.set_test_cookie()
    return render(request, "foo/login_form.html")
Changed in Django 5.1:

Support for setting test cookies in asynchronous view functions was added.

ビューの外でセッションを使う

注釈

このセクションの例では、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_REQUESTTrue に設定します。True に設定すると、Django は1リクエストごとに、セッションをデータベースに保存してくれるようになります。

セッションクッキーが送信されるのは、セッションの作成及び修正時のみであることに注意してください。SESSION_SAVE_EVERY_REQUESTTrue に設定すると、各リクエストごとにセッションクッキーが送信されるようになります。

同時に、セッションクッキーの expires 部分も、セッションクッキーが送信されるごとに更新されます。

レスポンスステータスコードが 500 の時には、セッションは保存されません。

ブラウザ起動中のみ有効なセッション vs. 永続的なセッション

SESSION_EXPIRE_AT_BROWSER_CLOSE 設定を使うと、セッションフレームワークが、ブラウザ起動中のみ有効なセッションと永続的なセッションのどちらを使うか設定できます。

デフォルトでは、SESSION_EXPIRE_AT_BROWSER_CLOSEFalse にセットされていて、セッションクッキーは SESSION_COOKIE_AGE の期間ユーザのブラウザに保持されます。これにより、ユーザはブラウザを開くたびにログインし直さずに済みます。

SESSION_EXPIRE_AT_BROWSER_CLOSETrue に設定すると、Django はブラウザ起動中のみ有効なクッキーを使用します。このクッキーは、ブラウザを閉じた瞬間に破棄されます。ユーザがブラウザを開くたびにログインするようにしたい場合には、この設定を利用してください。

この設定はグローバルなデフォルトですが、1セッションごとのレベルで設定を上書きできます。その場合には、上の using sessions in views で書いたようにして、request.sessionset_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 の毎日のジョブに追加するなどの方法で、定期的にこのコマンドを実行することが推奨されています。

キャッシュバックエンドの場合はこの問題が発生しないことに注意してください。キャッシュの場合、不要なデータは自動的に削除されるようになっているからです。しかし、クッキーバックエンドの場合はそうではありません。セッションデータはユーザのブラウザに保存されるからです。

セッションのセキュリティ

サイトのサブドメインからは、クライアントに対して、ドメイン全体に対するクッキーを設定できます。信頼できるユーザに管理されていないサブドメインから送られたクッキーを許可している場合、この方法でセッションの固定化 (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 で指定したモジュール内に置かれています。

All SessionStore subclasses available in Django implement the following data manipulation methods:

An asynchronous interface for these methods is provided by wrapping them with sync_to_async(). They can be implemented directly if an async-native implementation is available:

独自のセッションエンジンを作ったり、既存のものをカスタマイズするには、SessionBase か既存の SessionStore クラスを継承した新しいクラスを作る必要があるでしょう。

セッションエンジンを拡張することもできますが、データベースをバックエンドとするセッションエンジンを拡張するには通常、追加の労力が必要です(詳しくは次のセクションを参照してください)。

Changed in Django 5.1:

aexists(), acreate(), asave(), adelete(), aload(), and aclear_expired() methods were added.

データベースを使ったセッションを拡張する

Django に含まれるデータベースを使ったセッションエンジン (具体的に言えば、dbcached_db) をカスタマイズするには、AbstractBaseSession とともに SessionStore クラスを継承する必要があるかもしれません。

AbstractBaseSessionBaseSessionManager は、INSTALLED_APPSdjango.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 の盗難に対してサイトを脆弱にしてしまいます。

Back to Top