Token인증 적용하기

guava·2022년 1월 17일
0

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.

1. DRF에서 지원하는 인증


https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

rest_framework.authentication.SessionAuthentication

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

rest_framework.authentication.BasicAuthentication

  • 외부 서비스/앱에서 매번 username/password를 넘기는 것은 보안상 위험

rest_framework.authentication.TokenAuthentication

  • 초기에 username/password로 Token을 발급받고, 이 Token을 매번 API요청에 담아서 보내어 인증을 처리

2. Token


2.1. Token 모델


  • User모델과 1:1 관계
  • 각 User별 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에 값이 없으면, 랜덤 문자열을 지정.
            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

2.2. 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})

방법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):
    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>

2.3. Token 획득


설정하기

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),
]

2.3.1. HTTPie를 통한 Token 획득

  • 유저 미리 생성하기 → EP10 - Authentication과 Permission
  • 유저명, 암호를 통한 토큰 획득
    > 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"
    }

2.3.2. Token과 HTTPie 활용

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"

2.3.3. 파이썬에서의 처리

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, # 필히 띄워쓰기
}

# 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)

0개의 댓글