Contents
Introduction: Django REST frameworkでJWT認証を導入する意義
Django REST framework(DRF)におけるJWT認証の導入は、セキュリティと柔軟性の両立が求められる現代のWebアプリケーション開発において不可欠です。特にカスタムユーザモデルを採用することで、企業内システムでは一般的な社員番号(employee_number)フィールドを認証キーとして利用できるため、セキュリティリスクを低減できます。SimpleJWTは、非推奨となったDjango REST framework JWTライブラリに代わる公式サポートされた選択肢であり、トークンのカスタマイズやセキュリティ設定が簡単に行える点で優れています。
SimpleJWTのインストールと基本設定
DRF環境におけるSimpleJWT導入は、最小限の手順で実現可能です。以下にインストールと初期設定を解説します。
インストール手順
-
仮想環境アクティベート後、以下のコマンドを実行します。
bash
pip install djangorestframework-simplejwt -
INSTALLED_APPSにrest_framework_simplejwtを追加します。
settings.pyでの設定項目
SimpleJWTのカスタマイズには以下のような設定が必要です。主なオプションはSIMPLE_JWT辞書内に配置されます。
| 設定項目 | 値例 | 内容 |
|---|---|---|
ACCESS_TOKEN_LIFETIME |
datetime.timedelta(minutes=30) |
有効期間(アクセストークン) |
REFRESH_TOKEN_LIFETIME |
datetime.timedelta(days=7) |
有効期間(リフレッシュトークン) |
ALGORITHM |
'HS256' |
シグネチャに使用するアルゴリズム |
注意: リフレッシュトークンの有効期限は、セキュリティとユーザーエクスペリエンスを考慮して慎重に設定してください。
カスタムユーザモデルの作成と認証フロー設計
従来のAbstractUserモデルではカバーできない要件(例: 社員番号ベースのログイン)には、カスタムユーザモデルが必須です。以下に実装手順を示します。
AbstractUserからの継承
models.pyでAbstractUserを継承したモデルを作成します。
python
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
employee_number = models.CharField(max_length=20, unique=True)
- マイグレーションを実行します。
bash
python manage.py makemigrations
python manage.py migrate
社員番号フィールドの認証ロジック
ログイン時に社員番号を優先するため、認証用のget_user_model()関数やサードパーティライブラリのカスタム化が必要です。以下は認証フローの概要です。
- ユーザーが社員番号・パスワードでログインリクエストを送信
- 認証処理で
employee_numberとpasswordが一致するか検証 - 成功時、アクセストークンとリフレッシュトークンを発行
トークン発行処理のカスタマイズ
SimpleJWTのデフォルトではメールアドレスベースの認証が実装されているため、社員番号フィールド対応のカスタムが必要です。
TokenObtainPairViewの上書き
serializers.pyに以下のクラスを追加します。
python
from rest_framework_simplejwt.views import TokenObtainPairView
from .models import CustomUser
class CustomTokenObtainPairSerializer(TokenObtainPairView):
def get_user(self, username_field, password):
try:
user = CustomUser.objects.get(**{username_field: username_field})
except CustomUser.DoesNotExist:
raise exceptions.AuthenticationFailed('user not found')
if not user.check_password(password):
raise exceptions.AuthenticationFailed('password mismatch')
return user
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def validate(self, attrs): employee_number = attrs.get('employee_number') password = attrs.get('password') if not employee_number or not password: raise exceptions.AuthenticationFailed('employee_numberとpasswordが必要です。') try: user = self.get_user('employee_number', password) except exceptions.AuthenticationFailed as e: raise e return super().validate(attrs) |
custom_claimsの実装
トークンペイロードに社員番号を含めるには、get_tokenメソッドでカスタムします。
|
1 2 3 4 5 |
def get_token(cls, user): token = super().get_token(user) token['employee_number'] = user.employee_number return token |
認証ミドルウェアの独自実装
認証が確立された後は、リクエストごとにトークンを検証するミドルウェアを実装します。
トークン検証ロジック
middleware.pyに以下のコードを記述します。
python
from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
class CustomAuthenticationMiddleware:
def init(self, get_response):
self.get_response = get_response
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def __call__(self, request): auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return JsonResponse({'error': 'トークンがありません'}, status=401) try: token_str = auth_header.split(' ')[1] token = AccessToken(token_str) if token_str.startswith('access_') else RefreshToken(token_str) user = CustomUser.objects.get(employee_number=token['employee_number']) request.user = user except (AccessToken.DoesNotExist, RefreshToken.DoesNotExist): return JsonResponse({'error': '無効なトークンです'}, status=401) except CustomUser.DoesNotExist: return JsonResponse({'error': 'ユーザーが見つかりません'}, status=403) response = self.get_response(request) return response |
権限チェックの拡張
特定のAPIにアクセスを制限するには、@permission_classesデコレーターを使用します。
|
1 2 3 4 5 6 |
from rest_framework.permissions import BasePermission class IsEmployee(BasePermission): def has_permission(self, request, view): return hasattr(request.user, 'employee_number') |
リフレッシュトークンとセキュリティ対策
リフレッシュトークンの使用は、ユーザーがセッション切れでもスムーズにログインし直せる仕組みです。ブラックリスト機能はセキュリティを強化する鍵となります。
RefreshTokenの使い方
-
リフレッシュトークンを使用してアクセストークンを更新するフローです。
python
refresh = RefreshToken(token)
access_token = str(refresh.access_token) -
有効期間管理(例: 7日が推奨)は、
SIMPLE_JWT['REFRESH_TOKEN_LIFETIME']で設定します。
ブラックリスト機能の実装
ブラックリストモデルを追加し、使用済みトークンを登録することで、悪用されたトークンの再利用を防ぎます。
|
1 2 3 4 5 6 7 8 |
class BlacklistedToken(models.Model): token = models.CharField(max_length=500, unique=True) created_at = models.DateTimeField(auto_now_add=True) @classmethod def check_blacklist(cls, token_str): return cls.objects.filter(token=token_str).exists() |
エラーレスポンスのカスタマイズと実装検証
一貫したエラー応答は、開発者と運用チームにとって重要です。以下に具体的な例を示します。
ステータスコードとメッセージの統一
- 401 Unauthorized:
{"error": "認証情報が不正です"} - 403 Forbidden:
{"error": "権限がありません"} - 422 Unprocessable Entity:
{"error": "employee_numberまたはpasswordは必須です"}
テストケース例(Postmanで確認)
- 正常な認証リクエスト:
- URL:
/api/token/ - Method:
POST -
Body:
json
{
"employee_number": "E001",
"password": "secure_password"
} -
レスポンス:
json
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
} -
無効なトークン送信:
- Header:
Authorization: Bearer invalid_token - レスポンス:
json
{
"error": "無効なトークンです"
}
まとめと今後の展望
本記事では、Django REST frameworkにおけるJWT認証の導入方法とカスタマイズ手段について解説しました。特に社員番号を認証キーとして利用する仕組みや、ブラックリストによるセキュリティ強化などの重要なポイントを取り上げました。
- カスタムユーザモデルの設計と実装
- SimpleJWTによるトークン発行・検証フローのカスタマイズ
- 社員番号を用いた認証ロジックの実装
- リフレッシュトークンとブラックリスト機能の導入
- 一貫したエラーレスポンス設計
記事で解説したコードをベースに、自身のプロジェクトでJWT認証を導入してみましょう。