인증(Authentication)과 인가(Authorization)에 대해 배우며 로그인한 유저에 대한 권한을 JWT(Json Web Token)을 발행해주어 해결한다는 것을 확인했었다. HTTP Request에서 Client가 JWT를 헤더에 넣어 보내주면 Server는 Token을 통해 사용자에게 권한을 부여할 수 있다. 그런데 Token이 유효한지 매번 어떻게 확인할 수 있을까?
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가 권한이 필요한 특정 기능을 사용하기 전에 권한의 존재 여부를 판별해보자.
users
App 경로에 utils.py
를 새로 만들어 구현한 Decorator는 아래와 같다.
# 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
json
을 import 한다.jwt
를 import 한다.JsonResponse
, ObjectDoesNotExist
를 import 해서 DB에 값이 없는 경우 Error처리를 한다.SECRET_KEY
와 권한을 부여받은 User를 판별하기 위해 User
를 import한다.# 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인지 검증해보자.
id=13
인 User wecode
로 로그인 Response에 JWT를 생성한다.
이제 id=4
인 Post를 보기 위한 Request에 이 JWT를 첨부해 보내면, Server는 이 JWT를 복호화하여 User의 id를 얻고, 이 id의 권한을 확인하여 충분한 권한을 가지고 있으면 요청을 처리하고, 그렇지 않다면 Error를 반환한다.
- Token이 틀렸을 경우의 결과
- 제대로 Token이 첨부되었을 때 결과
Decorator를 이용해 제대로 로그인 권한 여부를 살펴볼 수 있었다!
사실 처음엔 제대로 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하고 decode
에 algorithms
를 넣어 해결했다. 해결하고 나니 느낀 것이, 문제가 발생하면 최대한 혼자 해결하려고 노력하는 편인데 좀 더 소통하고 질문을 한다면 더 빨리 해결해서 능률이 오르지 않을까 라는 것이었다. 위코드에서 강조한 것 처럼 커뮤니케이션을 위해 노력해야겠다.