【Django】セッション管理の実装ガイド|request.session の使い方・有効期限・バックエンド選択まで完全解説

スポンサードリンク



Django · Session Management · ユーザー状態管理

ッション管理は Web アプリの「ユーザーごとの状態を保持する」ための土台です。Django には強力なセッションフレームワークが標準で組み込まれており、request.session で辞書のように使えるのが魅力。本記事では、セッションの基本(取得・保存・削除)、ログイン状態の管理、データベースバックエンド/キャッシュバックエンドの選択、有効期限設定、セッション乗っ取り対策まで、実装に必要な全要素を網羅します。

この記事では、Djangoのユーザセッション管理機能の実装事例についてご紹介します。

user@sinyblog:~/article 01_section_1.md通常のユーザ認証機能の実装

まずは、通常のDjangoユーザ認証の実装を行います。

settings.pyの設定

まずは、認証系に必要なsettngis.pyの設定を追加します。

python


LOGIN_URL='/accounts/'

LOGIN_REDIRECT_URL='/accounts/'

LOGOUT_REDIRECT_URL='/accounts/login'

 

パラメータ 説明

LOGIN_URL

ログオンが必要なページに認証していないユーザがアクセスした場合にリダイレクトするURLを指定

LOGIN_REDIRECT_URL

ログオン後にリダイレクトされるURLを指定

LOGOUT_REDIRECT_URL

ログアウト後にリダイレクトされるURLを指定

ユーザ認証用アプリケーションの作成

以下のコマンドでユーザ認証用に新規アプリケーション(accounts)を作成します。

default


python manage.py startapp accounts 

 

また、settings.pyのINSTALLED_APPに「 'accounts.apps.AccountsConfig',」を追加しておきます。

urls.pyの設定

まず、プロジェクト直下のurls.pyを以下の通り設定します。

python


from django.contrib import admin

from django.urls import path,include

from django.conf.urls import include, url



urlpatterns = [

    path('admin/', admin.site.urls),

    path('accounts/', include('accounts.urls')),

]

 

続いて、accountsアプリケーション直下のurls.pyを以下の通り設定します。

python


from django.urls import path

from . import views



app_name = 'accounts'



urlpatterns = [

    path('login/', views.Login.as_view(), name='login'),

    path('logout/', views.Logout.as_view(), name='logout'),

    path('', views.index, name='index'),

]
  • 4行目:accountsアプリケーションのURLの名前空間名を指定しています。
  • 7行目:ログオン認証画面用のURLパターンを定義しています。(http://127.0.0.1/login)
  • 8行目:ログアウト用のURLパターンを定義しています。
  • 9行目:ログオン後のINDEXページを定義します。

 

ログオン用フォームの作成

accountsアプリケーション配下にforms.pyを新規作成し、以下の通り設定します。

python


from django.contrib.auth.forms import(

    AuthenticationForm

)



class LoginForm(AuthenticationForm):

    #ログオンフォームの定義

    def __init__(self, *args, **kwargs):

        super().__init__(*args, **kwargs)

        for fields in self.fields.values():

            fields.widget.attrs['class'] = 'form-control'

            fields.widget.attrs['placeholder']= fields.label

 

  • 1~3行目:Django標準のAuthenticationFormクラスを利用すると簡単にログオン用フォームを生成することができるため、django.contrib.auth.formsからAuthenticationFormをインポートしています。
  • 5行目:AuthenticationFormクラスを承継してログオン用のフォームクラス(LoginForm)を定義しています。
  • 9~11行目:ここでは以下の設定を行っています。

      ①全てのフォームの部品のclass属性に「form-control」を指定(bootstrapのフォームデザインを利用するため)

      ②全てのフォームの部品にpaceholderを定義して、入力フォームにフォーム名が表示されるように指定。

      ※上記のすべてのフォームとは、AuthenticationFormクラス内で定義されているfields(フォームのカラム:ユーザ名、パスワード)のことです。

      ※Djangoではウィジェット(widget)と呼ばれる HTML の入力エレメントを表現するためのオブジェク トが存在していて、field.widget.attrs[属性] = <設定値>という形でフォームのデザインを定義することができます。

 

accountsアプリケーションのviews.py設定

accountsアプリケーションのviews.pyに以下のコードを追記します。

python


from django.shortcuts import render

from django.contrib.auth.mixins import LoginRequiredMixin

from django.contrib.auth.views import(

    LoginView, LogoutView

)



from . forms import LoginForm



class Login(LoginView):

    #ログインページ

    form_class = LoginForm

    template_name = 'accounts/login.html'





class Logout(LoginRequiredMixin, LogoutView):

    #ログアウトページ

    template_name = 'accounts/login.html'





def index(request):

    

    return render(request, 'accounts/index.html')

 

  • 9行目:LoginViewクラスを承継して、ログオン用クラス(Login)を定義しています。
  • 11行目:form_classでログオンページに利用するフォームクラス(LoginForm)を指定しています。
  • 12行目:ログオンページ用のテンプレートファイルを指定しています。
  • 15行目:LoginRequireMixin、LogoutViewクラスを承継してログアウト用のクラス(Logout)を定義しています。

    LoginRequireMixinクラスを承継しているのは、ログオンしているユーザでないとログアウトボタンを押せないようにするためです。(LoginRequiremixinはログオンしているユーザだけが制御できるようにするためのクラス)

    template_nameでログアウト後に表示されるテンプレートファイルを指定します。

  • 20行目:ログオン後に遷移するテスト用のTOPページを定義した関数です。

今回は、特にモデルの定義はしないので、一旦このままマイグレーションを実行しておきます。

python


python manage.py makemigrations

python manage.py migrate

 

テンプレートの作成

まずは、テンプレートの場所をプロジェクト直下のtemplatesに変更するためにsettings.pyに以下の設定をします。

'DIRS': [os.path.join(BASE_DIR, 'templates')],

python


EMPLATES = [

    {

        'BACKEND': 'django.template.backends.django.DjangoTemplates',

        'DIRS': [os.path.join(BASE_DIR, 'templates')],  # DIRSに設定を入れる。

        'APP_DIRS': True,

        'OPTIONS': {

            'context_processors': [

                'django.template.context_processors.debug',

                'django.template.context_processors.request',

                'django.contrib.auth.context_processors.auth',

                'django.contrib.messages.context_processors.messages',

            ],

        },

    },

]

プロジェクトフォルダ直下に「\templates\accounts」を作成します。

続いて、base.html(共通テンプレート)、index.html(ログオン後のTOP画面),login.html(ログオン画面)を以下の通り作成します。

※テンプレート内の細かいデザインの設定部分は設定例ですので、必ずしも同じでなくてよいです。

■base.html

python


<!doctype html>

<html lang="ja">

<head>

    <!-- Required meta tags -->

    <meta charset="utf-8">

    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">



    <!-- Bootstrap CSS -->

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"

          integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">



    <title>Tutorialサイト</title>

</head>

<body>

    <!-- ナビバー -->

    <nav class="navbar navbar-expand-lg navbar-light bg-light">

      <a class="navbar-brand" href="{% url 'accounts:login' %}">Tutorialサイト</a>

      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">

        <span class="navbar-toggler-icon"></span>

      </button>



      <div class="collapse navbar-collapse" id="navbarSupportedContent">

        {% if user.is_authenticated %}

            <a class="btn btn-primary" href="{% url 'accounts:logout' %}">ログアウト</a>

        {% endif %}

      </div>



      

    </nav>



    <!-- メインコンテント -->

    <div class="container mt-3">

        {% block content %}{% endblock %}

    </div>



    <!-- Optional JavaScript -->

    <!-- jQuery first, then Popper.js, then Bootstrap JS -->

    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"

            integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"

            crossorigin="anonymous"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"

            integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ"

            crossorigin="anonymous"></script>

    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"

            integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm"

            crossorigin="anonymous"></script>

</body>

</html>
{% if user.is_authenticated %}  〜〜〜  {% endif %} で囲んだ箇所(ログアウトボタンの表示)は、ログイン中のユーザーにのみ表示されます。

認証済みユーザの場合、Userオブジェクトのis_authenticatedにTrueが設定されます。

■index.html

python


{% extends "accounts/base.html" %}

{% block content %}

ログオンしました。

{% endblock %}

 

■login.html

python


{% extends "accounts/base.html" %}

{% block content %}

<form action="" method="POST">

    <div class="col-md-6 offset-md-3">

        <div class="card">

            <div class="card-body">





                    {% for field in form %}

                    {{ field }}

                     <hr>

                  {% endfor %}



                {% for error in form.non_field_errors %}

                  <div class="alert alert-danger" role="alert">

                       <p>{{ error }}</p>

                  </div>

              {% endfor %}

                <button type="submit" class="btn btn-success btn-lg btn-block" >ログイン</button>

                <input type="hidden" name="next" value="{{ next }}" />

                {% csrf_token %}

            </div>

        </div>

    </div>

</form>

{% endblock %}
  • 9~12行目:テンプレートタグのfor文でformオブジェクトから要素を1つずつfield変数に格納し、{{field}}でフォームを表示しています。
  • 14~18行目: 認証時のエラーは form.non_field_errorsから取得できます。

    エラー内容は{{ error }}と記載することで表示できます。

  • 以下のようにfor文の中にdivのclassでalert-dangerを指定することでエラーが発生した場合に赤枠でエラー内容が表示されるように設定しています。

 

以上で、http://127.0.0.1:8000/accounts/login/にアクセス後にログオンすると、以下のようにログオン画面が表示されます。

ログオンすると、以下のようなTOP画面にログオンします。

さて、ここまでは通常のユーザ認証機能の実装でした。

いよいよ、本題のユーザセッション管理機能を実装していきます。

user@sinyblog:~/article 02_section_2.mdユーザセッション管理機能の実装

まず、必要なモジュールをpipでインストールします。

python


pip install django-user-sessions

 

settings.pyのINSTALLED_APPS に 'user_sessions' を追加します。

python


INSTALLED_APPS = [

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'user_sessions', #追加

    'accounts.apps.AccountsConfig',

]

 

settings.pyのMIDDLEWAREに「'user_sessions.middleware.SessionMiddleware',」を追加します。

python


MIDDLEWARE = [

    'django.middleware.security.SecurityMiddleware',

    'django.contrib.sessions.middleware.SessionMiddleware',

    'django.middleware.common.CommonMiddleware',

    'django.middleware.csrf.CsrfViewMiddleware',

    'django.contrib.auth.middleware.AuthenticationMiddleware',

    'django.contrib.messages.middleware.MessageMiddleware',

    'django.middleware.clickjacking.XFrameOptionsMiddleware',

    'user_sessions.middleware.SessionMiddleware',  #追加

]

 

また、settings.pyの末尾に「SESSION_ENGINE = 'user_sessions.backends.db'」を追加します。

次に、プロジェクト直下のurls.pyに以下の設定を追加します。

python


from django.contrib import admin

from django.urls import path,include

from django.conf.urls import include, url



urlpatterns = [

    path('admin/', admin.site.urls),

    path('accounts/', include('accounts.urls')),

    path('', include('user_sessions.urls', 'user_sessions')),  #追加

]

 

この時点で、再度「python manage.py migrate」を実行します。

以上で設定は完了です。

http://127.0.0.1:8000/account/sessions/にアクセスすると、以下のようなセッション管理画面が表示されます。

デフォルトだと上記画面の通り、ユーザ名が表示されていないのと、英語表記なのでカスタマイズしてみます。

上記画面のテンプレートをカスタマイズするには「Lib\site-packages\user_sessions\templates\user_sessions\session_list.html」を以下の通り修正します。

default


{% extends "user_sessions/_base.html" %}

{% load user_sessions i18n %}



{% block content %}

  {% trans "<em>unknown on unknown</em>" as unknown_on_unknown %}

  {% trans "<em>unknown</em>" as unknown %}



  <h1>{% trans "アクティブセッション一覧" %}</h1>



  <table class="table">

    <thead>

      <tr>

        <th>{% trans "IPアドレス" %}</th>

        <th>{% trans "ユーザ名" %}</th>

        <th>{% trans "デバイス" %}</th>

        <th>{% trans "最終アクション" %}</th>

        <th>{% trans "セッションの終了" %}</th>

      </tr>

    </thead>

    {% for object in object_list %}

      <tr {% if object.session_key == session_key %}class="active"{% endif %}>

        <td>{{ object.ip}}</td>

        <td>{{ object.user }} </td>

        <td>{{ object.user_agent|device|default_if_none:unknown_on_unknown|safe }}</td>

        <td>

          {% if object.session_key == session_key %}

            {% blocktrans with time=object.last_activity|timesince %}{{ time }} ago (this session){% endblocktrans %}

          {% else %}

            {% blocktrans with time=object.last_activity|timesince %}{{ time }} ago{% endblocktrans %}

          {% endif %}

        </td>

        <td>

          <form method="post" action="{% url 'user_sessions:session_delete' object.pk %}">

            {% csrf_token %}

            {% if object.session_key == session_key %}

              <button type="submit" class="btn btn-xs btn-link">{% trans "End Session" %}</button>

            {% else %}

              <button type="submit" class="btn btn-xs btn-warning">{% trans "End Session" %}</button>

            {% endif %}

          </form>

        </td>

      </tr>

    {% endfor %}

  </table>



  {% if object_list.count > 1 %}

    <form method="post" action="{% url 'user_sessions:session_delete_other' %}">

      {% csrf_token %}

      <p>{% blocktrans %}You can also end all other sessions but the current.

        This will log you out on all other devices.{% endblocktrans %}</p>

      <button type="submit" class="btn btn-default btn-warning">{% trans "End All Other Sessions" %}</button>

    </form>

  {% endif %}

{% endblock %}

 

{{ object.ip}}  → ロケーションは不要なのでIPだけ表示するように変更

{{ object.user }} → ユーザ名が表示されるように追加

再度、「http://127.0.0.1:8000/account/sessions/」にアクセスすると以下のような画面に変わります。

画面右側の「End Session」をクリックすると自分のセッションを切断することができます。

※上記管理画には自分のセッションしか表示されないようです。

上記画面は、自分自身のセッションしか切断できませんが、Adminサイト上からは全ユーザのセッションを切断することができます。

adminサイトにログオンすると「セッション」が追加されているのでクリックします。

ログオンしている全ユーザのセッション情報が表示されるので、切断したいユーザにチェックを入れてセッション切断を実行すれば、セッションをKILLすることができます。

以上、「Djangoユーザセッション管理画面の実装」でした。

参考になれば幸いです。

おすすめの記事