Djangoユーザセッション管理機能を実装してみた。

スポンサードリンク



こんにちは。sinyです。

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

通常のユーザ認証機能の実装

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

settings.pyの設定

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

LOGIN_URL='/accounts/'
LOGIN_REDIRECT_URL='/accounts/'
LOGOUT_REDIRECT_URL='/accounts/login'

 

パラメータ 説明

LOGIN_URL

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

LOGIN_REDIRECT_URL

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

LOGOUT_REDIRECT_URL

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

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

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

python manage.py startapp accounts 

 

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

urls.pyの設定

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

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を以下の通り設定します。

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を新規作成し、以下の通り設定します。

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に以下のコードを追記します。

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 manage.py makemigrations
python manage.py migrate

 

テンプレートの作成

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

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

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

<!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

{% extends "accounts/base.html" %}
{% block content %}
ログオンしました。
{% endblock %}

 

■login.html

{% 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画面にログオンします。

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

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

ユーザセッション管理機能の実装

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

pip install django-user-sessions

 

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

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',」を追加します。

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に以下の設定を追加します。

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」を以下の通り修正します。

{% 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ユーザセッション管理画面の実装」でした。

参考になれば幸いです。

おすすめの記事