環境
Windows 11 Home
Python 3.10.2
Django 4.0.2
venv利用あり
関連記事
Django 第1回:Django Custom User Model の作成
Django 第2回:Django 初回ログイン時にパスワード変更を強制する 今回
Django 第3回:Django 一定期間パスワードを変更していないユーザにパスワード変更を強制する
Django 第4回:Django ランダムかつ有効期限のあるURLを生成し、上位者に承認してもらいアカウントを発行する
Django 第5回:Django パスワード試行回数ロックとランダムかつ有効期限付きURLでの本人確認による解除
背景
Django利用時、パスワードをメール送信するがそのままそれを利用されたくないケースが存在する。
(セキュリティ的に、とか)
そういったケースでユーザの初回ログイン時にパスワードを強制変更させる機能を実装する。
前回の記事でカスタムユーザモデルを作成し、パラメータとしてpassword_changed
を用意しておいた。
今回は更にMiddlewareを開発し、ページアクセス時にpassword_changed
の値がFalse
(デフォルト値)であれば
パスワード変更ページにリダイレクトさせる。
状態
users/models.py
にカスタムユーザモデルを用いている。
password_changed
というパラメータが今回のキー。
前回の記事参照。
手順1
users
配下に password_middleware.py
を作成する。
以下のように記載する。
import re
from django.http import HttpResponseRedirect
from users.models import User # 作成したCustom User Model
# https://office54.net/python/django/middleware-make-myself
class ForcePasswordChangeMiddleware:
"""
初期パスワードを初回のログオン時に強制的に変更させるMiddleware。
'password_changed'がFalseであれば、未変更とみなしパスワード変更ページにリダイレクト。
パスワード変更後は'password_changed'をTrueにする
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if re.match(r'/admin/password_change/done/?', request.path):
try:
# refererが'http://xxx.xxx.xxx.xxx:xx/admin/password_change/'かどうかを確認する
# すなわちpassword変更されたならば、Userステータスを更新する
# HOSTNAMEの場合は正規表現を変更してください
referer = request.environ.get('HTTP_REFERER')
print("referer:",referer)
if re.match(r'^https?://((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]).*/admin/password_change/?', referer):
user = User.objects.get(email=request.user.email)
user.password_changed = True
user.save()
except:
pass
return response
def process_view(self, request, view_func, view_arts, view_kwargs):
# ログアウトは許可する
if re.match(r'^/admin/logout/?', request.path):
return
# 初回ログイン時にパスワード変更を強いる
if re.match(r'^/admin/?', request.path) and \
not re.match(r'^/admin/password_change/?', request.path):
try:
# password_changeがFalseであれば、パスワード変更ページにリダイレクト
# すなわち初回ログイン時にパスワード変更を強いる
password_changed = request.user.password_changed
if password_changed == False:
return HttpResponseRedirect('/admin/password_change/')
except:
pass
(2022/07/09 : http, https どちらでもいいようにre.matchの構文を修正)
解説1
def __call__(self, request):
...
の部分はview関数が実行された後に呼び出される。
パスワード変更完了時=/admin/password_change/done/ に遷移した際に、HTTP_REFERERが
http://xxx.xxx.xxx.xxx:xx/admin/password_change/'かどうかをチェックしている。
IPアドレスおよびポートがドメイン名である場合などは正規表現を修正する必要がある。
また/admin/の部分も変えることができるため、変更した方はこちらも修正が必要。
def process_view(self, request, view_func, view_arts, view_kwargs):
# ログアウトは許可する
if re.match(r'^/admin/logout/?', request.path):
return
# 初回ログイン時にパスワード変更を強いる
if re.match(r'^/admin/?', request.path) and \
...
process_viewではビュー関数を呼ぶ前にフックされる。
ページに遷移する前にこの時点で強制的にリダイレクト(パスワード変更)させてしまう。
手順2
settings.py
に今作成したmiddlewareを追加する。
MIDDLEWARE = [
'users.password_middleware.ForcePasswordChangeMiddleware', # 追加
'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',
]
解説2
'users.password_middleware.ForcePasswordChangeMiddleware'は
'App名.pyファイル名.class名'を意味している。
できるだけページが読まれる前に処理をしてほしいので、今回は先頭に追加している。
動作確認
1.現状
password_changed.py
には値がない=Falseの状態
2.ログアウトする
3.ログイン
4.ログイン後
いつもの管理画面が見える前に、パスワード変更画面になった。(成功)
5.パスワード変更後
各ページにアクセスすることができる。
アカウントのパラメータを見てみると、password_changed.py
にチェック=True
の状態に更新されている。
備考
定期的、例えば3か月おきにpassword_changed.py
の値をFalse
にすることで、
定期的にパスワード変更を促すことができる。定期的な処理については別の機会に紹介したい。
参考
【Django】Middlewareミドルウェアを新たに自分で作成(自作)する方法
【Django】Middlewareミドルウェアとは(有効化、構造、実行されるタイミング)
Django 第1回:Django Custom User Model の作成
Django 第2回:Django 初回ログイン時にパスワード変更を強制する
Django 第3回:Django 一定期間パスワードを変更していないユーザにパスワード変更を強制する
Django 第4回:Django ランダムかつ有効期限のあるURLを生成し、上位者に承認してもらいアカウントを発行する
Django 第5回:Django パスワード試行回数ロックとランダムかつ有効期限付きURLでの本人確認による解除