일하는/Cloud, Web

Django REST Framework API Key 사용법

김논리 2021. 6. 25. 16:03

가입된 사용자 별로 API 키를 발급하고 해당 API 키를 이용하여 인증하는 REST API 구현해 보자.

https://www.django-rest-framework.org

 

Home - Django REST framework

 

www.django-rest-framework.org

https://florimondmanca.github.io/djangorestframework-api-key/

 

Django REST Framework API Key

 Introduction Django REST Framework API Key is a powerful library for allowing server-side clients to safely use your API. These clients are typically third-party backends and services (i.e. machines) which do not have a user account but still need to i

florimondmanca.github.io

Installation

Django REST Framwork와 Django REST Framework API Key를 설치한다.

(env) $ pip install djangorestframework
(env) $ pip install djangorestframework-api-key

Django 프로젝트 설정

Django project와 application을 생성하고, settings.py 파일에 아래 내용을 추가한다.

...
# Application definition
INSTALLED_APPS = [
    ...
    "rest_framework",
    "rest_framework_api_key",
    ...
]
...
# Restframework
REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework_api_key.permissions.HasAPIKey",
    ]
}

DEFAULT_PERMISSION_CLASSES를 설정해 두면, REST Framework로 구성된 API의 기본 permission이 된다.

구현

Views

 

API Key로 인증하는 API를 만들어 보자. views.py 파일에 다음과 같이 추가한다.

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['GET'])
def health(request):
    return Response({'message': 'OK'}, status=status.HTTP_200_OK)

 

앞서 설정 파일에서 DEFAULT_PERMISSION_CLASSES  HasAPIKey 로 설정해 두었기 때문에, 모든 API는 따로 Permission class를 설정하지 않는 한, API Key 인증을 시도하게 된다.

 

 

만약 DEFAULT_PERMISSION_CLASSES 를 설정하지 않고, 개별 API 마다 설정하고 싶다면, 다음과 같이 추가할 수 있다.

...
from rest_framework.decorators import permission_classes
from rest_framework_api_key.permissions import HasAPIKey

@api_view(['GET'])
@permission_classes([HasAPIKey])
def health(request):
    return Response({'message': 'OK'}, status=status.HTTP_200_OK)

 

 

다음으로, API Key를 조회하고, 발급하는 API를 만들어 보자. views.py 파일에 다음과 같이 추가한다.

...
from datetime import datetime, timedelta
from rest_framework_api_key.models import APIKey
from rest_framework.permissions import IsAuthenticated

@api_view(['POST', 'GET'])
@permission_classes([IsAuthenticated])
def apikey(request):
    username = request.user.username
    if request.method == 'GET':
        response = {}
        keys = APIKey.objects.all()
        for key in keys:
            if key.name == username:
                response['apikey'] = key.prefix + '*****'
                response['created'] = key.created
                response['expires'] = key.expiry_date
                break
        return Response(response, status=status.HTTP_200_OK)
    else:
        keys = APIKey.objects.all()
        for key in keys:
            if key.name == username:
                key.delete()
                break
        api_key, key = APIKey.objects.create_key(name=request.user.username)
        expires = datetime.now() + timedelta(days=90)
        api_key.expiry_date = expires
        api_key.save()
        return Response({'apikey': key, 'expires': expires}, status=status.HTTP_200_OK)

 

Permission class는 IsAuthenticated 로 설정하여, 사용자 ID와 비밀번호를 통한 인증을 수행한다.

 

GET 요청이 올 경우, API Key 들 중에 사용자 이름을 갖는 키의 정보를 전달한다.

 

POST 요청이 올 경우, 기존에 사용자 이름을 갖는 키가 있을 경우, 이를 삭제하고 사용자 이름을 이름으로 갖는 키를 생성한다. 해당 키는 90일 뒤에 expire되도록 설정하고, 생성된 키 값과 함께 expire 정보를 전달한다.

 

사용자 정의 API Key 모델을 사용하여 User 와 API Key 1:1 관계를 정의해야 할 것 같음. → 아래 Customize에서 진행하도록 하자.

URLs

 

만들어진 뷰를 URL로 연동해 보자. urls.py 파일에 다음과 같이 작성한다.

from django.urls import path
from . import views

app_name = 'api'

urlpatterns = [
    path('health', views.health),
    path('apikey', views.apikey),
]

테스트

우선 API Key 없이 health API를 호출해 보자.

$ curl -X GET 127.0.0.1:8000/api/health

아래와 같이 Django REST framework에서 발생시키는 오류 결과를 확인할 수 있다.

{
    "detail": "자격 인증데이터(authentication credentials)가 제공되지 않았습니다."
}

 

API Key를 발급해 보자. (API Key 값을 발급 당시에만 확인할 수 있으며, 추후에는 확인이 불가능하니, 적절한 곳에 잘 기록해 둬야 한다.)

$ curl -X POST --user 사용자아이디:비밀번호 127.0.0.1:8000/api/apikey

아래와 같이 apikey 값과 만료일자가 수신됨을 확인할 수 있다.

{
    "key":"hgHg2XK7...",
    "expires":"2021-09-21T14:32:26.015103"
}

 

이제 발급된 키 값을 이용하여 다시 health API를 호출해 보자.

$ curl -X GET \
  --header "Authorization: Api-Key hgHg2XK7..." \
  127.0.0.1:8000/api/health

 

아래와 같이 API Key를 이용한 인증이 수행되고, 정상적으로 결과값이 수신됨을 확인할 수 있다.

{
    "message": "OK"
}

 

Customize APIKey Model

API Key키를 User Model과 OneToOneField를 이용하여 관계를 지어 보자. 한 명의 사용자에게 하나의 API Key만 발급될 수 있도록!!

Models

 

models.py 파일에 AbstractAPIKey 를 상속한 Custom한 API Key 모델을 생성한다.

from django.db import models
from django.contrib.auth import get_user_model
from rest_framework_api_key.models import AbstractAPIKey

class UserAPIKey(AbstractAPIKey):
    user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name="apikey")

    class Meta(AbstractAPIKey.Meta):
        ordering = ['user']
        verbose_name = "User API key"

django.contrib.auth에서 제공하는 get_user_model helper를 이용하여 User Model을 가져온다. 

related_name을 이용하여 User 모델을 통해서 API Key model에 접근할 수 있도록 한다.

 

model이 변경되었으므로, makemigrations 명령을 이용하여 migration을 해야 한다.

Permissions

permission에 사용되는 rest_framework_api_key.permissions HasAPIKey 는 built-in APIKey에만 동작한다. 즉, 사용자 정의 API Key Model을 만들어 사용하는 경우, Permission class 또한 생성하여 API Key를 검증해야 한다. permissions.py 파일을 생성하고, BaseHasAPIKey를 상속받아 Permission class를 구현한다.

from rest_framework_api_key.permissions import BaseHasAPIKey
from .models import UserAPIKey

class HasUserAPIKey(BaseHasAPIKey):
    model = UserAPIKey

다음, 해당 Permission class를 사용하도록 views.py 파일을 수정한다.

...
from .permissions import HasUserAPIKey

@api_view(['GET'])
@permission_classes([HasUserAPIKey])
def health(request):
    return Response({'message': 'OK'}, status=status.HTTP_200_OK)

Views

views.py 파일을 수정하여 rest_framework_api_key.models APIKey가 아닌 앞서 만든 UserAPIKey 모델을 사용하도록 한다.

...
from .models import UserAPIKey
...
@api_view(['POST', 'GET'])
@permission_classes([IsAuthenticated])
def apikey(request):
    user = request.user
    if request.method == 'GET':
        if hasattr(user, 'apikey'):
            apikey = user.apikey
            return Response({
                'prefix': apikey.prefix,
                'created': apikey.created,
                'expires': apikey.expiry_date
            }, status=status.HTTP_200_OK)
        return Response({}, status=status.HTTP_204_NO_CONTENT)
    else:
        if hasattr(user, 'apikey'):
            user.apikey.delete()
        api_key, key = UserAPIKey.objects.create_key(name=request.user.username, user=user)
        expires = datetime.now() + timedelta(days=90)
        api_key.expiry_date = expires
        api_key.save()
        return Response({'apikey': key, 'expires': expires}, status=status.HTTP_200_OK)

hasattr 을 이용하여 User와 연결된 API Key가 있는지 확인한다. 키 생성 요청의 경우, 이미 해당 User에게 키가 존재할 경우, 삭제하고 생성한다.

Admin

관리자 페이지에서 키 관리를 할 수 있도록 추가해 줄 수도 있다. admin.py 파일에 다음과 같이 추가한다.

from django.contrib import admin
from rest_framework_api_key.admin import APIKeyModelAdmin
from rest_framework_api_key.models import APIKey
from .models import UserAPIKey

admin.site.unregister(APIKey)

@admin.register(UserAPIKey)
class UserAPIKeyModelAdmin(APIKeyModelAdmin):
    pass

기존에 default로 사용되는 APIKey는 삭제하고, 생성한 UserAPIKey를 등록한다.