チュートリアルを使用したdjangoの勉強と並行して、djangoで構成している本サイトのポータルページもある程度のセキュリティー対策が急がれる。djangoを素で使用しているサイトは少ないようでwordpressのようにBOTによる攻撃はまだ見られない。何らかのCMSを使用しているのを見越して適当なURLをぶっ込んでくるBOTは404で弾けるのであまり問題にならないが、ごくたまーに、しつこくadminでログインしようとする輩が現れるようだ。
このWordpressのサイトでも結構多かったので(普通に見に来てくれる人は殆ど居ないのにwww)、User Login Historyというプラグインを入れている。ここもたまに100回以上のadminログイン試行をしてくる輩がいるので、このログを利用してIPを特定しblockset.pyで手動だけどお引き取り願うことにしている。
同じような仕組みをdjangoのポータルページにも仕掛けようと勉強がてら色々と調べたところ、ログイン用の認証バックエンドという仕組みが利用できそうだということが分かった。デフォルトの<project>/setting.pyには明示的に定義はされていないが(django.conf.global_settings にデフォルト定義)、AUTHENTICATION_BACKENDSという配列がありここのカスタムや追加の認証バックエンドを追加出来る(<project>/settings にて再定義)
デフォルトでは’django.contorib.auth.backends.ModelBackend’が一つ定義されているので、こちらを継承カスタマイズして認証結果をログ出力できるようにする方針とする。
django.contrib.auth.backends.ModelBackend 抜粋
class ModelBackend(BaseBackend):
"""
Authenticates against settings.AUTH_USER_MODEL.
"""
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
・・・・・・
認証判定にはメソッドauthenticateが機能しており、認証OKであればusernameを、NGであればNoneを返す関数であるため、クラス継承により下記のようにメソッドをオーバーライドして再定義する
<project>/backends.py
from django.contrib.auth.backends import ModelBackend
from logging import getLogger # using logging module
class ModelLogBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
logger = getLogger('loginInfo') # example
res = super().authenticate(request, username, password, **kwargs)
if res is None:
logger.info('login faild, bad user: %s' % username)
else:
logger.info('user logged in : %s' % username)
return res
super()で親クラスのメゾッドに丸投げして、結果に応じてログを吐き出す機構を追加。継承したクラスを新たなバックエンドとして<project>/settings.pyに定義する。
# Authntication backend
AUTHENTICATION_BACKENDS = [
'<project>.backends.ModelLogBackend'
]
<project>はdjangoのプロジェクト名に置き換える。
ちなみにloggerの設定は別の記事を参照して完成しておくこと。当サイトの場合は他のアクセスログの都合でstructlogを使用している(設定さえしておけばloggingと同等に使用可能)。
もともと、ポータルとしてのdjangoサイトは出来上がっていたので、この機能を付けたくてdjangoの本格勉強を始めたという流れでした。蓋を開けてみると思ったより簡単で、同時にカスタマイズ性の高さに今更ながら感動です。次はdjabngo+bootstrap(japascript)による動的サイトの実装実験を目指したいな。