파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.
https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
username/password
를 넘기는 것은 보안상 위험username/password
로 Token을 발급받고, 이 Token을 매번 API요청에 담아서 보내어 인증을 처리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
# 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})
일종의 콜백처럼 동작한다.
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)
# 본 명령은 생성된 Token을 변경하지 않습니다. 필수는 아님
python3 manage.py drf_create_token <username>
# 강제로 Token 재생성하기
python3 manage.py drf_create_token -r <username>
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
],
}
# 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),
]
쉘> 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"
}
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, # 필히 띄워쓰기
}
# 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)