(DRF) Token Authentication

duo2208·2022년 2월 5일
0

Django

목록 보기
22/23
post-thumbnail

Token Authentication


이전에 살펴 본 DRF에서 지원하는 인증 중에 TokenAuthentication 이 있습니다. SessionAuthentication 과 BasicAuthentication 은 아무래도 아래와 같은 문제점이 있다보니 TokenAuthentication 을 쓰는 편입니다.

  • SessionAuthentication
    • rest_framework.authentication.SessionAuthentication
    • 세션을 통한 인증.
    • 웹 프론트엔드와 장고가 같은 호스트를 쓴다면 세션 인증 사용 가능. (ex. nginx)
    • Django의 default. 외부 서비스에서는 세션 인증 불가능.
  • BasicAuthentication
    • rest_framework.authentication.BasicAuthentication
    • Basic 인증 헤더를 통한 인증.
    • Django의 default. 외부 서비스에 매번 usernamepassword 를 넘기는 것은 보안상 위험함.
  • TokenAuthentication
    • rest_framework.authentication.TokenAuthentication
    • Token 헤더를 통한 인증.
    • 초기에 usernamepassword 으로 Token 을 발급받고, 이 Token 을 매 API요청에 담아 보내 인증을 처리.



Token


Token 모델

  • User 모델과 1:1 관계
  • 각 User별 Token은 수동으로 생성해 줘야함.
  • Token은 User별로 유일하며, Token만으로 인증을 수행함.

🦝 django-rest-framework/rest_framework/authtoken/models.py

# rest_framework/authtoken/models.py

class Token(models.Model):
    """
    The default authorization token 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:
        # Work around for a bug in Django:
        # https://code.djangoproject.com/ticket/19422
        #
        # Also see corresponding ticket:
        # https://github.com/encode/django-rest-framework/issues/705
        abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS
        verbose_name = _("Token")
        verbose_name_plural = _("Tokens")

    def save(self, *args, **kwargs):
        if not self.key:	# self.key에 값이 없으면 랜덤 문자열 지정
            self.key = self.generate_key()
        return super().save(*args, **kwargs)

    @classmethod
    def generate_key(cls):
        return binascii.hexlify(os.urandom(20)).decode()

    def __str__(self):
        return self.key



Token 사용해보기


1. Token 생성

Token을 생성하는 방법은 3가지가 있습니다. 그 중에 ObtainAuthToken 방법을 이용해 생성해 볼겁니다.

먼저, DRF에서 지원해주는 authtoken 앱을 설정해줍니다. 앱의 일종이며 Token 이라는 model을 지원하므로 migrate 과정도 필요합니다. Django 에서는 default Authentication 으로 SessionAuthenticaton 과 BasicAuthentication 만을 사용하고 있으므로, TokenAuthentication 도 추가해줍니다.

# settings.py

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

# ...

RESTFRAMEWORK = {
	'DEFAULT_AUTHENTICATION_CLASSES': [
    	'rest_framework.authentication.SessionAuthenticaiotn',
        'rest_framework.authentication.BasicAuthenticaiotn',
        'rest_framework.authentication.TokenAuthentication',
    ],
}
$ python manage.py migrate

+ 방법1) ObtainAuthToken APIView 를 통한 흭득 및 생성

이 방법은 URL Pattern 매핑이 필요합니다.

# rest_framework/authtoken/views.py

class ObtainAuthToken(APIView):
	def post(self, request, *args, **kwargs):	# post 요청이 들어오면
    	#...
        token, created = Token.objects.get_or_create(user=user)	# 해당 user의 token을 가져오거나, 아직 token이 없으면 token을 생성한다.
        return Response

+ 방법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() : sender 변수에 User 모델을 지정 해서 해당 모델이 save 될 때 호출한다.
@receiver(post_save, senders=settings.AUTH_USER_MODEL):
def create_auth_token(sener, instance=None, created=False, **kwargs):
	if created:	# create/update 모두 save 이므로, created 일때만 token을 생성하게 한다.
    	Token.objects.create(user=instance)

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

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

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

2. Token 흭득

+ obtain_auth_token 노출

token 흭득을 API endpoint 로 노출시키기 위해 URL Pattern 매핑을 진행합니다.

# accounts/urls.py

from rest_framework.authtoekn.views import obtain_auth_token

urlpatterns = [
	path('api-token-auth/', obtain_auth_token),
]

+ HTTPie를 통한 Token 흭득

shell 에서 올바른 password와 함께 POST 요청을 보내면 token 이 생성된 것을 확인할 수 있습니다. 이 때 계속되는 요청에 대해선 토큰을 계속 생성하는 것이 아닌, 이미 만들어뒀던 토큰을 반환합니다.

> http POST localhost:8000/accounts/api-token-auth/ username=<username> password=<password>
HTTP/1.1 200 OK
Allow: POST, OPTIONS
Content-Length: 52
Content-Type: application/json
Cross-Origin-Opener-Policy: same-origin
Date: Sat, 05 Feb 2022 11:53:31 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.10.2
Vary: Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "token": "ff199ba3d83de440a31e55c48494f906c8cfae2e"
}

+ Token과 HTTPie 활용 (bash)

shell 에서 다음과 같은 명령어를 사용하면 token 값을 저장시킬 수 있습니다.

> export HOST="http://localhost:8000"
> export TOKEN="ff199ba3d83de440a31e55c48494f906c8cfae2e"

이제 더 간단히 요청을 보낼 수 있습니다.

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

3. Token 사용

이제 post 요청시에 token을 넘기지 않으면 인증이 필요하다는 메시지가 나타납니다.

> http localhost:8000/post/
HTTP/1.1 403 Forbidden
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Cross-Origin-Opener-Policy: same-origin
Date: Sat, 05 Feb 2022 12:19:58 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.10.2
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "detail": "Authentication credentials were not provided."
}

token을 넘겨주면 정상적으로 요청이 넘어옵니다.

> http localhost:8000/post/ "Authorization: Token $Token"
HTTP/1.1 200 OK
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Length: 175
Content-Type: application/json
Cross-Origin-Opener-Policy: same-origin
Date: Sat, 05 Feb 2022 12:14:55 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.10.2
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "author_username": "django",
    "created_at": "2022-01-26T02:08:59.293032Z",
    "ip": null,
    "is_public": false,
    "message": "first posting",
    "pk": 1,
    "updated_at": "2022-01-26T02:08:59.293032Z"
}

📌 참고 출처

0개의 댓글