TIL 34 | Authorization With Decorator

임종성·2021년 7월 28일
2

Django

목록 보기
14/17
post-thumbnail

인증(Authentication)과 인가(Authorization)에 대해 배우며 로그인한 유저에 대한 권한을 JWT(Json Web Token)을 발행해주어 해결한다는 것을 확인했었다. HTTP Request에서 Client가 JWT를 헤더에 넣어 보내주면 Server는 Token을 통해 사용자에게 권한을 부여할 수 있다. 그런데 Token이 유효한지 매번 어떻게 확인할 수 있을까?

Decorator

Decorator는 특정 함수나 메소드 위에 붙어, 함수나 메소드가 실행되기 전에 먼저 실행되어 사전에 하고 싶은 작업을 전처리하는 함수라고 할 수 있다. 만약 데코레이터를 사용하지 않는다면 우리는 Token이 유효한지 확인하는, 예를 들어 아래와 같은 코드를 권한이 필요한 모든 View Class에 추가해야 할 것이다.

	    access_token = request.headers.get('Authorization', None)          
            payload = jwt.decode(access_token, SECRET_KEY, algorithms='HS256')  
            user = User.objects.get(id=payload['id'])                 
            request.user = user                                               

하지만 우리는 다음과 같이 함수 위에 Decorator를 붙여 편의성을 높일 수 있다.

class PostView(View):
    @SignInDecorator
    def post(self, request):
        try:
            data = json.loads(request.body)

이제 SignInDecorator를 작성해서 User가 권한이 필요한 특정 기능을 사용하기 전에 권한의 존재 여부를 판별해보자.

SignInDecorator

users App 경로에 utils.py를 새로 만들어 구현한 Decorator는 아래와 같다.

Modules

# users.utils.py

import json, jwt

from django.http 	    import JsonResponse
from django.core.exceptions import ObjectDoesNotExist        

from westagram.settings     import SECRET_KEY                             
from users.models           import User
  • HTTP Request를 Python 언어와 연결해주기 위해 json을 import 한다.
  • Header를 통해 받은 Json Web Token을 decode해야하기때문에 jwt를 import 한다.
  • Django 자체 모듈로 JsonResponse, ObjectDoesNotExist를 import 해서 DB에 값이 없는 경우 Error처리를 한다.
  • Decode에 필요한 SECRET_KEY와 권한을 부여받은 User를 판별하기 위해 User를 import한다.

Wrapper

# users.utils.py

def SignInDecorator(func):
    def wrapper(self, request, *args, **kwargs):
        try:
            # HTTP Request의 Header로부터 JWT를 받는다.
            access_token = request.headers.get('Authorization', None)          
            # JWT를 발급할때 사용한 SECRET_KEY와 알고리즘으로 Token을 Decode한다.
            payload      = jwt.decode(access_token, SECRET_KEY, algorithms='HS256')  
            # Decode한 JWT로부터 User ID를 알아내어 권한을 부여받은 User를 저장한다.
            user   	 = User.objects.get(id=payload['id'])                 
            request.user = user                                                
	# Token이 적합하지 않은 경우 INVALID_TOKEN 에러를 반환한다.
        except jwt.exceptions.DecodeError:                                        
            return JsonResponse({'message' : 'INVALID_TOKEN', 'Token':access_token }, status=400)
	# User가 존재하지 않은 경우 INVALIDE_USER 에러를 반환한다.
        except User.DoesNotExist:                                         
            return JsonResponse({'message' : 'INVALID_USER'}, status=400)

        return func(self, request, *args, **kwargs)

    return wrapper

이제 작성한 Decorator를 적용하여 제대로 토큰을 발행받았는 User인지 검증해보자.

GET Post With JWT

id=13인 User wecode로 로그인 Response에 JWT를 생성한다.

이제 id=4인 Post를 보기 위한 Request에 이 JWT를 첨부해 보내면, Server는 이 JWT를 복호화하여 User의 id를 얻고, 이 id의 권한을 확인하여 충분한 권한을 가지고 있으면 요청을 처리하고, 그렇지 않다면 Error를 반환한다.

- Token이 틀렸을 경우의 결과

- 제대로 Token이 첨부되었을 때 결과

Decorator를 이용해 제대로 로그인 권한 여부를 살펴볼 수 있었다!


TIL / Decode Error

사실 처음엔 제대로 Decorator를 작성한 후, 발급된 Token을 그대로 Request의 Header에 넣어 요청했는데도 INVALIUD_TOKEN이 뜨며 Error가 났었다. 혹시 JWT의 Type이 다른가? decode할때는 byte 타입을 넣어야 하나? 라는 고민까지 하며 여러가지 실험을 해봤는데 아무리 해도 decode가 되지 않았다.

  def wrapper(self, request, *args, **kwargs):
       try:
           access_token = request.headers.get('Authorization', None)          
           payload = jwt.decode(access_token, SECRET_KEY, algorithm='HS256')  
           user = User.objects.get(id=payload['id'])                 
           request.user = user                                                

       except jwt.exceptions.DecodeError:                                        
           return JsonResponse({'message' : 'INVALID_TOKEN', 'Token':access_token }, status=400)

       except User.DoesNotExist:                                         
       ...

INVALID_TOKEN이 반환된다는 것은 except로 넘어갔다는 것인데 원인을 모르니.. 참 답답했다.

그러다 오늘 세션에서 연우님과 성훈님이 문제가 발생했을때 하나하나 print하며 원인을 발견하는 것이 좋다고 한것이 떠올랐다. 그래서 SignInView에서 Token을 Decode해서 출력해봤다.

# users/views.py
# SignInView Class
access_token = jwt.encode({'id' : user.id}, SECRET_KEY, algorithm = 'HS256')
access_token = jwt.decode(access_token, SECRET_KEY, algorithm = 'HS256')

return JsonResponse({"MESSAGE":"SUCCESS", "TOKEN":access_token}, status=200)

그랬더니 이런 오류가 나왔다..

바로 구글에 jwt.exception.DecodeError ~~를 검색하니.. pyJWT의 'Known Issue' 라면서 2.0이상이 아니라 1.7 Version을 사용하면 해결이 된다고 했다. 아마 2.0 Version은 Encode시 str 타입으로 반환하는데 1.7은 byte 타입으로 반환하는 데서 생긴 오류? 가 아닐까 라고 생각했다.

그런데 동기분들이랑 이야기를 하고 나니, jwt.encode에는 argument가 algorithm인데 jwt.decode는 argument가 algorithms로 하면 된다고 했다.. 에러 내용에 "algorithms" argument when ~~ 라고 적혀있는데 제대로 봤으면 더 빨리 해결했을 것 같았다.

어쨌든 1.7 Version으로 바꿨던 걸 다시 Update하고 decodealgorithms를 넣어 해결했다. 해결하고 나니 느낀 것이, 문제가 발생하면 최대한 혼자 해결하려고 노력하는 편인데 좀 더 소통하고 질문을 한다면 더 빨리 해결해서 능률이 오르지 않을까 라는 것이었다. 위코드에서 강조한 것 처럼 커뮤니케이션을 위해 노력해야겠다.

profile
어디를 가든 마음을 다해 가자

0개의 댓글