この記事は何?
Django で Django REST Framework によるAPI開発を行う際、ユーザークラスではなく自作のクラス(デバイスなど)に紐づけたAPI Keyを発行し、APIで認証を行う方法のメモです。
環境
- django 4.2
- djangorestframework 3.14.0
- djangorestframework-api-key 2.3.0
やりたいこと
Django WEBアプリ上で端末情報を登録し、APIへのアクセスを登録された端末からのみに制限する、ということをしたいです。
ここでは、Django REST framework を使用してAPIを実装し、Django REST Framework API Key を使用してAPIキーによるアクセス制限を行います。
- https://www.django-rest-framework.org/
- https://github.com/florimondmanca/djangorestframework-api-key
Django REST framework にはトークン認証を行う TokenAuthentication が標準で含まれていますが、こちらは DjangoのUserクラス に紐づいてキーが発行されるようなので、今回は使用していません。
実装例
model.py
端末を表す Device クラス、端末に紐づける DeviceAPIKey クラス、アクセス権限を与えるための HasDeviceAPIKey クラスを定義します。
from django.db import models
from rest_framework_api_key.models import AbstractAPIKey
from rest_framework_api_key.permissions import BaseHasAPIKey
class Device(models.Model):
"""登録するデバイス情報"""
id = models.AutoField("ID", primary_key=True)
name = models.CharField("デバイス名", max_length=50, )
created_at = models.DateTimeField("作成日時", auto_now_add=True)
updated_at = models.DateTimeField("更新日時", auto_now=True)
def __str__(self):
return f"{self.name}"
class DeviceAPIKey(AbstractAPIKey):
"""デバイスに紐づけるAPIキークラス"""
device = models.ForeignKey(
Device,
on_delete=models.CASCADE,
related_name="api_keys",
)
class HasDeviceAPIKey(BaseHasAPIKey):
"""認証用クラス"""
model = DeviceAPIKey
キー発行view例
デバイス登録時にAPIキーを発行し、画面に表示します。ユーザーは画面に表示されたキー情報を保存しておき、アクセス時に使用します。
def device_create(request):
if request.method == "POST":
device_form = DeviceForm(request.POST)
if device_form.is_valid():
device = device_form.save(commit=False)
# Deviceモデルに対してAPIキーを発行,紐づけ
_, api_key = DeviceAPIKey.objects.create_key(name=device.id, device=device)
context = {"api_key": api_key}
return render(request, "Devices/device_create_complete.html", context=context) #デバイス登録完了画面で デバイスに対して発行した API Key を表示する.
else:
device_form = DeviceForm()
return render(request, "Devices/device_create.html") #デバイス登録画面表示
API View
permission_classes に HasDeviceAPIKey を指定することで、発行されたAPIキーがリクエストヘッダーに含まれていない場合、アクセスを拒否することができます。
また、ヘッダに含まれるキー情報から, キーに紐づくデバイスを取得することもできます。
from devices.models import Device, DeviceAPIKey HasDeviceAPIKey
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
class DeviceAPIView(APIView):
permission_classes = [HasDeviceAPIKey] # DeviceAPIKey で発行したトークンがHeadersに含まれる場合のみアクセスを許可
def get(self, request):
# key に紐づくdeviceを取得
api_key = request.META.get("HTTP_X_API_KEY")
key = DeviceAPIKey.objects.get_from_key(api_key)
device = Device.objects.get(api_keys=key)
print(f"device {device.name} conntected.")
return Response({"message": "Success"}, status=status.HTTP_200_OK)
settings.py
以下を追記し、リクエスト時のヘッダー名を X-Api-Key に変更しています。
# header key名を X-Api-Keyに変更
API_KEY_CUSTOM_HEADER = "HTTP_X_API_KEY"
リクエスト
登録後に表示された APIkey をリクエストヘッダーに含めて送信します。
APIKeyが不正な場合、アクセスが拒否され403レスポンスが返却されます。
url = "api_url"
api_key="your key"
headers = {"X-Api-Key": api_key}
response = requests.get(url, headers=headers)
その他
- 自身で検証はしていませんが、以下のソースを見ると、キー作成時に expiry_date を指定することで、キーの有効期限を指定することも可能そうです。
- 通信時は HTTPS で暗号化する必要があります。
まとめ
ユーザー以外のモデルと紐づけてAPIのアクセス制限を行う方法を記載しました。
より高度な認証などが必要な場合は、この方法では不十分なので別の方法が必要です。