DRF Token인증 적용하기

2022. 7. 15. 16:35강의 정리/Django REST Framework

반응형

DRF에서 지원하는 인증


rest_framework.authentication.SessionAuthentication

  • 웹 프론트엔드와 장고가 같은 호스트를 쓴다면, 세션 인증을 사용할 수 있습니다.(nginx 등 활용)
  • 외부 서비스 / 앱에서 세션 인증을 사용할 수 없다. // 안드로이드나 React등에서 사용할 수 없다.

 

rest_framework.authentication.BasicAuthentication

  • 외부 서비스/앱에서 매번 username/password를 넘기는 것은 보안상 위험하고, 하면 안된다.

 

rest_framework.authentication.TokenAuthentication

  • 초기에 username/password로 Token을 발급받고, 이 Token을 매번 API요청에 담아서 보내어 인증을 처리
  • 다폴트로 설정되어있지 않기에 설정을 해주어야 함, 추천(세션, 토큰)

 

 


 

 

Token


Token 모델


https://github.com/encode/django-rest-framework/blob/master/rest_framework/authtoken/models.py

 

GitHub - encode/django-rest-framework: Web APIs for Django. 🎸

Web APIs for Django. 🎸. Contribute to encode/django-rest-framework development by creating an account on GitHub.

github.com

 

  • User모델과 1:1 관계
  • 각 User별 Token은 수동으로 생성해줘야 합니다. -> Token 모델 객체를 따로 세팅해줘야 한다. 
  • Token은 User별로 유일하며, Token만으로 인증을 수행합니다.
class Token(models.Model):
    key = models.CharField(_("Key"), max_length=40, primary_key=True)
    user = models.OneToOneField(
            settings.AUTH_USER_MODEL, related_name='auth_token',
            on_delete=models.CASCADE, verbose_name=_("User"))
            created = models.DateTimeField(_("Created"), auto_now_add=True)

    class Meta:
        abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS

    def save(self, *args, **kwargs):
        if not self.key: # self.key에 값이 없으면, 즉 유저가 Token이 없다면 Key 필드에 랜덤 문자열을 지정.
            self.key = self.generate_key()
        return super().save(*args, **kwargs)

    def generate_key(self):
        return binascii.hexlify(os.urandom(20)).decode()

    def __str__(self):
        return self.key

 

 


 

Token 생성


방법 1) ObtainAuthToken 뷰를 통한 획득 및 생성 -> URL Pattern 매핑 필요

# rest_framework/authtoken/views.py
class ObtainAuthToken(APIView):
    def post(self, request, *args, **kwargs):  # post 요청일 때
        # ...
        # 토큰, 생성여부(bool 반환)
        token, created = Token.objects.get_or_create(user=user) # 유저가 있으면 가져오고, 없으면 토큰을 생성한다.
        return Response({'token': token.key})

 

ObtainAuthToken 내부 코드

 

class ObtainAuthToken(APIView):
    throttle_classes = ()
    permission_classes = ()
    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
    renderer_classes = (renderers.JSONRenderer,)
    serializer_class = AuthTokenSerializer
    if coreapi is not None and coreschema is not None:
        schema = ManualSchema(
            fields=[
                coreapi.Field(
                    name="username",
                    required=True,
                    location='form',
                    schema=coreschema.String(
                        title="Username",
                        description="Valid username for authentication",
                    ),
                ),
                coreapi.Field(
                    name="password",
                    required=True,
                    location='form',
                    schema=coreschema.String(
                        title="Password",
                        description="Valid password for authentication",
                    ),
                ),
            ],
            encoding="application/json",
        )

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data,
                                           context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        return Response({'token': token.key})


obtain_auth_token = ObtainAuthToken.as_view()

 

 

 

방법 2) Signal을 통한 자동 생성 -> 일종의 콜백과 같이 동작

from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token

@receiver(post_save, sender=settings.AUTH_USER_MODEL)  # 유저 모델을 지정해서 해당 모델이 save가 될 때 호출
def create_auth_token(sender, instance=None, created=False, **kwargs): # created 인자를 받는다.
    if created:  # create / update 모두 save이기 때문에, created 일때만 참일 때 동작하도록 수정
        Token.objects.create(user=instance)

 

 

 

방법 3)  Management 명령을 통한 생성

# 본 명령은 생성된 Token을 변경하지 않습니다. 필수는 아님
python3 manage.py drf_create_token <username>

# 강제로 Token 재생성하기
python3 manage.py drf_create_token -r <username>

 

 

 


 

Token 획득


Settings에 설정하기

INSTALLED_APPS = [
    # Django Apps
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Third-Party Apps
    'rest_framework',
    'rest_framework.authtoken',  # <-- Here

    # Local Apps (Your project's apps)
    'myapi.core',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',  # <-- And here
    ],
}

 

 

obtain_auth_token을 노출하기

# settings.py
INSTALLED_APPS = [
    # ...
    'rest_framework.authtoken',
]

# python manage.py migrate

# accounts/urls.py
from rest_framework.authtoken.views import obtain_auth_token

urlpatterns += [
    path('api-token-auth/', obtain_auth_token),
]
  • account 프로젝트에 설정하는 것을 추천한다.
  • 반드시 migrate를 해주어야 한다.

 

 

HTTPie를 통해 Token을 획득하기

쉘> http POST http://주소/accounts/api-token-auth/ username="유저명" password="암호"
HTTP/1.0 200 OK
Allow: POST, OPTIONS
Content-Type: application/json
Date: Fri, 01 Dec 2017 06:49:35 GMT
Server: WSGIServer/0.2 CPython/3.6.1
{
"token": "9cdd705c0a0e5adb8671d22bd6c7a99bbacab227"
}
  • 위 토큰을 통해 요청시 더 이상 username과 password를 넘기지 않아도 된다. 

 

 

 Token과 HTTPie 활용 (Bash)

export HOST="http://localhost:8000"
export TOKEN="9cdd705c0a0e5adb8671d22bd6c7a99bbacab227"

# Post List
http GET $HOST/api/post/ "Authorization: Token $TOKEN"

# Post Create
http POST $HOST/api/post/ "Authorization: Token $TOKEN" message="hello"

# Post Create with Photo
http --form POST $HOST/api/post/ "Authorization: Token $TOKEN" message="hello" photo@"f1.jpg"

# Post#16 Detail
http GET $HOST/api/post/16/ "Authorization: Token $TOKEN"

# Post#16 Update
http PATCH $HOST/api/post/16/ "Authorization: Token $TOKEN" message="patched"
http PUT $HOST/api/post/16/ "Authorization: Token $TOKEN" message="updated"

# Post#16 Delete
http DELETE $HOST/api/post/16/ "Authorization: Token $TOKEN"

 

 

파이썬에서의 처리

import requests  # pip install requests

HOST = 'http://localhost:8000'
res = requests.post(HOST + '/api-token-auth/', {
    'username': '유저명', # FIXME: 기입해주세요.
    'password': '암호', # FIXME: 기입해주세요.
})
res.raise_for_status()

token = res.json()['token']
print(token)

"""이제 인증이 필요한 요청에는 다음 헤더를 붙여주세요"""
headers = {
    'Authorization': 'Token ' + token, # 필히 띄워쓰기
}
  • header에 토큰을 담아서 보냄

 

# Post List
res = requests.get(HOST + '/api/post/', headers=headers)
res.raise_for_status()
print(res.json())

# Post Create
data = {'message': 'hello requests'}
res = requests.post(HOST + '/api/post/', data=data, headers=headers)
print(res)
print(res.json())

# Post Create with Photo
files = {'photo': open('f1.jpg', 'rb')}
data = {'message': 'hello requests'}
res = requests.post(HOST + '/api/post/', files=files, data=data, headers=headers)
print(res)
print(res.json())

# Post#16 Detail
res = requests.get(HOST + '/api/post/', headers=headers)
res = requests.get(HOST + '/api/post/16/', headers=headers)
res.raise_for_status()
print(res.json())

# Post#16 Patch
res = requests.patch(HOST + '/api/post/16/', headers=headers, data={'message': 'hello'})
res.raise_for_status()
print(res.json())

# Post#16 Update
res = requests.put(HOST + '/api/post/16/', headers=headers, data={'message': 'hello'})
res.raise_for_status()
print(res.json())

# Post#16 Delete
res = requests.delete(HOST + '/api/post/16/', headers=headers, data={'message': 'hello'})
res.raise_for_status()
print(res.ok)
반응형

'강의 정리 > Django REST Framework' 카테고리의 다른 글

DRF JWT 인증  (0) 2022.07.15
DRF Pagination  (0) 2022.07.14
DRF Authentication과 Permission  (0) 2022.07.14
DRF Serializer를 통한 유효성 검사 및 저장  (0) 2022.07.14
From과 Serializer 관점에서 DRF 비교  (0) 2022.07.13