1차 프로젝트: 중간 정리 (1)

signet·2021년 2월 21일
0

데이터베이스 구성을 종료하고, 기능별 구현을 시작하였다. 이 과정에서 로그인 데코레이터 구현, 외부 API연동, list comprehension사용법을 익혔다. 이 중 로그인 데코레이터에 관련된 부분을 작성해보고자 한다.

1. 로그인 데코레이터

사이트를 사용하다보면, 특정한 유저임을 전제로 하고 기능을 사용할 수 있도록 한 경우들이 있다. 예를 들어, 글을 작성하려면 어떤 유저가 작성자인지 알아야하는데, 작성할 때 자동적으로 로그인한 상태의 유저 정보를 끌어온다거나, 특정 유저의 결제 정보를 불러온다거나 하는 일이다. 하지만 http통신은 원칙적으로 상태를 유지하지 않는다. 즉, 로그인을 한번 했더라도 그 로그인한 정보를 유지하지는 않는 것이다. 그렇다고 로그인이 필요한 기능을 사용할 때마다 로그인을 요구할 수 는 없는 노릇이다.
이때 파이선의 데코레이터 기능을 이용하면 해결 할 수 있다. 데코레이터란, 어떤 함수나 클래스가 실행되기에 앞서 실행이 되도록 적용한 함수를 말한다. 즉, 로그인 데코레이터는 로그인이 필요한 클래스가 실행되기 전에 로그인을 했는지 안 했는지를 검증하는 함수가 실행되도록 하여 로그인이 된 상태인지, 되었다면 어떤 사용자인지 판별하여 주는 것이다.
로그인 데코레이터는 로그인시 사용자에게 발행한 토큰을 이용하여 사용자를 판별한다. 비록 로그인 정보는 저장할 수 없지만 사용자의 토큰은 저장하고 있을 수 있기 때문이다. 코드의 개괄은 다음과 같다.

def login_decorator(func):
    1     def wrapper(self, request, *args, **kwargs):
    2     ¦   if 'Authorization' not in request.headers:
    3     ¦   ¦   return JsonResponse({"MESSAGE":"NEED_LOGIN"}, status=401)
    4 
    5     ¦   access_token = request.headers['Authorization']
    6     ¦
    7     ¦   try:
    8     ¦   ¦   data         = jwt.decode(access_token, SECRET_KEY, ALGORITHM)
    9     ¦   ¦   user         = User.objects.get(id = data['user'])
   10     ¦   ¦   request.user = user
   11     ¦
   12     ¦   except jwt.DecodeError:
   13     ¦   ¦   return JsonResponse({"MESSAGE":"INVALID_TOKEN"}, status=401)                                                                                                                              
   14     ¦                                                                                                                                                                                                 
   15     ¦   except User.DoesNotExist:                                                                                                                                                                     
   16     ¦   ¦   return JsonResponse({"MESSAGE":"UNKNOWN_USER"}, status=401)                                                                                                                               
   17                                                                                                                                                                                                       
   18     ¦   return func(self, request, *args, **kwargs)                                                                                                                                                   
   19                                                                                                                                                                                                       
   20     return wrapper                      

함수 자체는 이중으로 작성한다. 이는 토큰 자체는 암호화되어 있긴 하지만, 토큰의 암호화 방식이 노출되거나 하는 경우, 개인정보가 새어나갈 우려가 있을 수 있다. 이를 방지하기 위해 토큰 자체에 유의미한 정보를 넣지 않긴 하지만, 아예 토큰이 노출되지 않도록 중첩을 이용하여 연산이 내부 함수에서만 이루어지고 바깥 함수에는 결과만을 노출하도록 하여 안전을 기하는 것이라 추정된다.
우선, 최초로 로그인을 한번 하여 토큰을 받았는지를 확인할 필요가 있다. 대부분의 경우 생성하여 반환받은 토큰을 Authorization에 저장하고 있는데, 이것이 존재하면 로그인을 한 것이고, 그렇지 않으면 로그인 자체가 되어있지 않은 것이기 때문에, 로그인이 필요하다는 메세지를 보낼 필요가 있다. 이를 구현한 것이 2~3번째 줄이다.
다음으로는 토큰을 확인하기 위해 요청의 헤더에 있는 토큰을 변수에 저장할 필요가 있다. 이것이 5번이다.
이후에, 토큰을 실제로 확인하여 어떤 유저인지 확인하고 그 유저의 정보를 보내주어야 한다. 토큰을 생성할 때, 보통 jwt를 이용하므로 이를 이용하여 복호화해주면 된다. 복호화를 할 때 필요한 정보는 토큰, 생성시의 시크릿 키, 생성시의 알고리즘이다.
이때, 토큰의 형식이 달라 복호화가 안 되는 경우와 토큰을 복호화 할 수는 있으나 그 안의 유저정보가 존재하지 않는 유저인 경우의 에러메세지를 처리해준 것이 12~16번째 줄이다.
토큰을 복호화하는 일에 성공하였을 경우, 그 안의 유저정보가 존재하는지, 존재한다면 어떤 유저인지 확인해줄 필요가 있다. 이는 유저테이블의 데이터를 get명령어로 불러오는 것이 가능한지 여부로 확인한다. 이때, 토큰을 생성할 때 그 안에 들어갈 유저의 정보는 그 유저임을 알려줄 수 있는 정보면 무엇이든지 가능하지만, 대부분의 경우 해킹 당했을 경우의 안전성을 위하여 큰 의미가 없는 정보인 id로 하는 것이 좋다.
그렇게 유저정보를 담고있는 객체를 불러왔다면, 그 정보를 리퀘스트의 헤더에 담아 보내줄 필요가 있다. 왜냐하면, 토큰을 통해 로그인이 되었다는 것을 확인한 이후, 어떤 유저인지 알아야 그 이후의 기능에 필요한 정보를 불러올 수 있기 때문이다.
이 모든것을 리턴으로 반환해주면 된다. 그 다음은 로그인이 필요한 기능의 클래스에 데코레이터로 붙여주면 된다.

0개의 댓글