Django REST Framework の認証・権限・レート制限の実装ガイド

Django REST Framework(DRF)におけるアクセス制御の3つのコア機能——ユーザー認証、操作権限付与、およびリクエスト頻度制限——は、APIのセキュリティと信頼性を支える基盤です。これらは独立して適用可能であり、組み合わせることで細かいアクセス制御が実現できます。

実行フローの優先順位

DRFでは、各リクエストに対して以下の順序で処理が行われます:認証 → 権限チェック → レート制限。この順序は変更不可であり、たとえば権限判定は認証が成功した後にのみ実行されます。

カスタム認証クラスの構築

独自の認証ロジックを実装するには、rest_framework.authentication.BaseAuthentication を継承します。

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from myapp.models import ApiToken

class TokenBasedAuth(BaseAuthentication):
    def authenticate(self, request):
        auth_header = request.META.get('HTTP_AUTHORIZATION')
        if not auth_header or not auth_header.startswith('Bearer '):
            raise AuthenticationFailed('認証ヘッダーが不正です')

        token_value = auth_header[7:]  # 'Bearer ' を除去
        try:
            token = ApiToken.objects.select_related('user').get(value=token_value)
            if not token.is_valid():
                raise AuthenticationFailed('トークンの有効期限が切れています')
            return token.user, token
        except ApiToken.DoesNotExist:
            raise AuthenticationFailed('無効なトークンです')

このクラスは、ビュークラスで個別に指定することも、settings.py でグローバルに設定することも可能です:

# views.py
from rest_framework.viewsets import ModelViewSet

class ProtectedResourceView(ModelViewSet):
    queryset = Resource.objects.all()
    serializer_class = ResourceSerializer
    authentication_classes = [TokenBasedAuth]  # 局所的適用
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'myapp.auth.TokenBasedAuth',
    ],
}

権限制御の実装

権限クラスは rest_framework.permissions.BasePermission を継承し、has_permission() メソッドで許可条件を定義します。

from rest_framework.permissions import BasePermission

class RoleBasedPermission(BasePermission):
    def has_permission(self, request, view):
        user = request.user
        if not user.is_authenticated:
            return False

        # 管理者なら全操作許可
        if user.is_staff:
            return True

        # 一般ユーザーはGETのみ許可(ただし特定アクションは除外)
        if request.method == 'GET':
            return True

        # POST/PUT/DELETEは特定のビューでのみ許可
        if view.action in ['update_profile', 'delete_draft']:
            return user.has_perm('myapp.change_userprofile')

        return False

権限クラスは、ビュークラスの permission_classes 属性で指定します。複数クラスを並べると、すべてが True を返す必要があります(AND条件)。

レート制限のカスタマイズ

DRFは SimpleRateThrottle を提供しており、IPアドレス単位での制限が簡単に実装できます。

from rest_framework.throttling import SimpleRateThrottle

class PerIPThrottle(SimpleRateThrottle):
    scope = 'ip_limit'
    rate = '10/h'  # 1時間あたり最大10回

    def get_cache_key(self, request, view):
        return self.get_ident(request)  # IPアドレスをキーとして使用

より高度な制御が必要な場合、BaseThrottle を直接継承し、allow_request() をオーバーライドします。以下は、Redisを用いた分散環境対応の例です:

import redis
from rest_framework.throttling import BaseThrottle

class RedisThrottle(BaseThrottle):
    def __init__(self):
        self.redis_client = redis.Redis(host='localhost', port=6379, db=1)

    def allow_request(self, request, view):
        client_id = self.get_ident(request)
        key = f"throttle:{client_id}:{view.__class__.__name__}"
        now = int(time.time())

        # 5分間のウィンドウでカウンタを管理
        window_start = now - 300
        pipeline = self.redis_client.pipeline()
        pipeline.zremrangebyscore(key, 0, window_start)
        pipeline.zcard(key)
        pipeline.zadd(key, {now: now})
        pipeline.expire(key, 300)
        _, count, _, _ = pipeline.execute()

        return count <= 20  # 5分間に20回まで

このレート制限クラスは、ビューに直接適用できます:

class SensitiveActionView(APIView):
    throttle_classes = [RedisThrottle]

    def post(self, request):
        # 敏感な操作(例:パスワード変更)
        pass

タグ: Django DRF authentication permissions rate-limiting

10月7日 02:01 投稿