待ってたんだよこれを！！ Django REST Frameworkでログイン時のアカウントロック機能を実現するクールな方法！

## 概要
Django REST Frameworkとdjango-axesを使用して、ログイン試行に失敗した場合のアカウントロック（アカウント凍結）機能を実装する方法を説明します！

## 使用技術
```pyproject.toml
python = "^3.11.2"
Django = "^4.2.7"
djangorestframework = "^3.14.0"
pydantic = "^1.10.7"
gunicorn = "^21.2.0"
django-axes = "^6.1.1"
```

## 説明する前の前提
:::note warn
コードの細部やDjango REST Framework自体の説明はあまりしません
また、インポートもとのコードが不要な場合は明記していません
ご了承ください🙇
:::



## django-axesとは

:::note info
Djangoフレームワーク内でログイン試行の監視やアカウントロック、IPアドレスのブラックリスト等の機能を提供してくれる多機能なライブラリです
:::

https://django-axes.readthedocs.io/en/latest/


## 早速コードを見ていきましょう！

まずは設定から
```settings.py

INSTALLED_APPS = [
    #　以下を追加　他は省略
    "axes",
]

MIDDLEWARE = [
    # 以下を追加　他は省略
    "axes.middleware.AxesMiddleware",
]

AUTHENTICATION_BACKENDS = [
    # 以下を追加
    "axes.backends.AxesBackend",
    "django.contrib.auth.backends.ModelBackend",
]

```
settings.pyにてdjango-axesをアプリに読み込んでもらうように各記述を追記していきましょう




```environment_variable.py
from pydantic import BaseSettings


class AxesVariableSettings(BaseSettings):
    """django-axes関連の環境変数を取得する設定クラス"""

    AXES_FAILURE_LIMIT: int = 5
    """ロックされるまでのログイン回数"""
    AXES_COOLOFF_TIME: float = 1
    """自動でロックが解除されるまでの時間"""
    AXES_LOCKOUT_PARAMETERS: list = ["username"]
    """ロック対象をusernameで判断する"""
    AXES_RESET_ON_SUCCESS: bool = True
    """ログインに成功したら失敗回数をリセットする"""
    AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT: bool = False
    """Trueの場合、ロックアウト中にログインに失敗した場合、クールオフ期間がリセット"""
```
例えば上記のように環境変数は設定できます
以下で指定できるオプションを確認できます

https://django-axes.readthedocs.io/en/latest/4_configuration.html



```common/axes_signal.py
from axes.signals import user_locked_out
from django.dispatch import receiver
from rest_framework.exceptions import PermissionDenied


@receiver(user_locked_out)
def axes_signal(*args, **kwargs):
    raise PermissionDenied("アカウントは凍結されてるよ。ちょっと待ってね")

```

ライブラリが提供するuser_locked_out シグナルをキャッチし、そのイベントが発生した際に特定の処理を実行するためのものです

```app/apps.py
from django.apps import AppConfig


class ApplicationConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "app"

    def ready(self):
        from common import axes_signal
```

`ready` メソッドは、アプリケーションが起動したときに実行される初期化コードを提供します。この特定の `ready` メソッドでは、`axes_signal` 関数をインポートしています

特に、`user_locked_out` シグナルが発生したときに、アカウントが凍結されたことを示すメッセージを含んだ `PermissionDenied` 例外を発生させます


## 準備は整いました！！動きを確認してみましょう🔥


アカウントロック状態はaccess_attemptテーブルで管理されています
今は何もレコードがありません

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515002/7e07238b-53d9-b609-0604-eb9d9a201431.png)

1回ログインに失敗するとレコードが生成されます
failures_since_startが1となっています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515002/d2985708-fad5-0bae-5e61-bfc07ddbdf6f.png)

5回失敗してみました
これでfailures_since_startが5回となり、先ほど設定した失敗した旨のエラーレスポンスが返却されます
この状態で正しいユーザーID、パスワードでログインしようとしても凍結されているのエラーが返ってきます👍
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515002/49afd793-7d64-6a11-2b5c-1351bb68ddd6.png)

:::note info
AXES_COOLOFF_TIMEで指定した時間が経過するか、該当レコードを削除すると
アカウントロックは解除されます
:::

なので、アカウントロック解除機能を実現するには該当のレコードを指定して、削除すれば実装できます


## 最後に
流れさえ、抑えればスムーズに開発が進められると思います🐰
アカウントロック機能について説明されている記事は少なかったので参考になれば嬉しいです
ぜひ、お試しを！