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