로그인을 해서 받은 토큰을 확인하는 loger을 만들어보자.
우선 로그인이 필요한 서비스를 정의한다.
이번에는 댓글페이지에 접근해서 로그인이된 유저(로그인해서 토큰을 발행받은유저)만 댓글을 달 수 있는 기능을 만들어보겠다.
우선 유저는 endpoint로 name(유저아이디), content(댓글)을 바디에, token정보를 headers에 첨부해서 보낼 것이다.
#replys/view.py
class ReplyView(View):
@Authentificator
def post(self, request):
data = json.loads(request.body)
Reply(
name = data['name'],
content = data['content']
).save()
return HttpResponse(status = 200)
request는 경로를 따라 ReplyView클래스의 post매서드로 온다. 그리고 post매서드를 작동시키기 전에 Authentificator이라는 데코레이터로 이동한다.
def Authentificator(func): # 데코레이터가 걸린 매서드를 arrument로 가져옴. 즉, func함수를 파라미터랑 싹다같이 가져옴
def wrapper(self,request, *args, **kwargs): # 조건이 되는 부분을 wrapper함수로 감싼다.(여기서 해당 매서드의 func 파라미터 싹다가져옴, 클래스니깐 self도 가져옴, 들어온 request랑 다른 요청 다가져오기위해 *args, **kwargs해준다.)
requested_token = request.headers.get('Authorization', None)
# header에서 Authorization 값을 가져옴. header은 body와다르게 json이 아님. header가 어떻게 들어오는지 request객체를 열어봐야함. request객체안에 header값을 get해서 Authorization키가 있으면 가져오고 >없으면 None을 반환. try except처리 안해줘도 되서 편한 매서드
encode_token = requested_token.encode('utf-8')
decrypted_token = jwt.decode(encode_token, 'wecode', algorithm = 'HS256') # 가져온 Authorization값을 jwt로 decode해주어 유저정보를 얻어냄
try:
if Account.objects.filter(name = decrypted_token['name']).exists(): # 만약 decode해서 읽어온 유저정보가 db에 exist하면
return func(self,request, *args, **kwargs) # arrument로 가져온 func()를 리턴함, 가져올때 다가져왔으니 리턴도 싹다해줌.
return JsonResponse({'message' : 'INVALID_TOKEN'}, status = 403) # 값이 다르거나 없는경우는 메시지와 403을 return
except InvalidSignatureError:
return JsonResponse({'message' : 'IVALID_TOKEN'}, status = 401)
return wrapper
Authentificator데코레이터는 accounts 즉 유저정보와 관련되기 때문에 유저데이터 정보가 담긴 accounts app에 파일로 저장해둬 필요시 범용적으로 사용되도록 한다.
이 데코레이터에서 주목할 점은 replys/reply(댓글을 담당하는 endpoint)로 온 request를 어떻게 데코레이터의 parameter로 가져오느냐 이다.
코드가 request를 처리하는 순서대로 타고가면서 분석해보자.
def Authentificator(func):
def wrapper(self,request, *args, **kwargs):
Authentificator란 decorator의 parameter로 ReplyView를 가져온다. 그리고 wrapper라는 함수를 만들어 ReplysView의 request를 가져온다. request에 어떤게 담겨져 올지 모르기 때문에 모든 인자를 받는다는 의미에서 *args와 **kwargs를 parameter로 넣어주고, Authentificator함수 또한 ReplyView클래스 안의 decorator로 작용되는 함수이기 때문에 self parameter또한 받아준다.
requested_token = request.headers.get('Authorization', None)
이제 request의 헤더값에 첨부된 Authorization값을 불러올 차례이다. request의 객체 headers안에 Authorization이라는 key를 호출해서 value를 불러오는 매서드이며, 많약 value값이 없으면 None을 가져온다. None을 value로 가져옴으로 인해서 value가 없을 때 발생할 error처리를 할 필요가 없게 해주는 것이 핵심이다.
그렇다면 request의 header는 어떻게 구성될까?
위의 사진은 메인코드를 전부 주석처리하고 request.headers 객체 자체를 리턴한 값이다. content-length, content-type등 여기서 언급된 headers를 구성하는 값들의 key가 담겨져왔다.
위의 사진은 request.headers.values()를 통해서 value값을 전부 리턴한 것이다. 각각의 key에 해당하는 value들이 구분없이 나열되어 있다.
다시 본론으로 넘어가서
encode_token = requested_token.encode('utf-8')
decrypted_token = jwt.decode(encode_token, 'wecode', algorithm = 'HS256')
request.headers.get('Authorization',None)으로 가져온 토큰값(string type)을 비교하기위해 byte type으로 encode해준다. 그리고 encode한 토큰, secretkey, 알고리즘종류를 인자로 입력하여 토큰을 디코드 시켜 유저 정보를 얻어낸다.(이 유저정보는 토큰을 만들 때 여기서넣었던 name)
이제 얻어낸 유저name의 값을 db에서 비교해야한다.
if Account.objects.filter(name = decrypted_token['name']).exists():
return func(self,request, *args, **kwargs)
return JsonResponse({'message' : 'INVALID_TOKEN'}, status = 403)
얻어낸 유저정보(name)에 해당하는 value값이 데이터베이스에 있으면 로그인된 유저이기 때문에 그대로 다음 로직의 함수인 ReplyView를 실행해서 리턴해준다. 물론 처음에 데코레이터로 들어올 때 받아왔던 self, request, *args, **kwargs를 parameter로 넣어 똑같이 리턴해준다.
마지막으로 발생되는 error를 잡아줄 차례이다.
except InvalidSignatureError:
return JsonResponse({'message' : 'IVALID_TOKEN'}, status = 401)
return wrapper
except로 발생할 수 있는 error들을 처리해주고 wrapper함수를 리턴한다.