Project 1. Marketkurly clone | 로그인 엔드포인트 구현 & JWT 인증 데코레이터

Hyeonju L.·2020년 12월 25일
0

Project

목록 보기
7/8
post-thumbnail

실제 구현 화면

1. 로그인 API

로그인 API는 회원가입 API에서 정보만 받아오면 되기때문에 비교적 간단하다. 처음엔 아이디와 비밀번호가 틀린경우 INVALID_ACCOUNT, INVALID_PASSWORD라는 메시지를 각각 리턴하도록 구현했는데, 개인정보보호 등의 이유로 인해 아이디와 비밀번호 에러메시지를 구분하지 않는 추세 라는 피드백을 받고 INVALID_USER로 통일하여 메시지를 리턴하였다.

구현사항

  • 에러처리
    - 아이디가 존재하지 않는 경우 USER_NOT_EXIST 메시지 리턴
    - 아이디 또는 비밀번호가 틀린 경우 INVALID_USER 메시지 리턴
    - 기타 에러 발생 시 KEY_ERROR 메시지 리턴
  • 로그인 토큰(JWT) 생성
    - 생성된 토큰의 내용(payload)에는 만료시간이 포함되는데 만료시간을 24시간으로 지정하여 작성(datetime.utcnow()+timedelta(hours=24))
    - 성공 시 access_token 리턴
class SigninView(View):
    def post(self, request):
        data = json.loads(request.body)

        try:
            if not User.objects.filter(account=data['account']).exists():
                return JsonResponse({"message":"USER_NOT_EXIST"}, status=404)

            if User.objects.filter(account=data['account']).exists():
                user      = User.objects.get(account=data['account'])
                hashed_pw = bcrypt.hashpw(data['password'].encode('utf-8'), bcrypt.gensalt()).decode('utf-8')

                if bcrypt.checkpw(data['password'].encode('utf-8'), user.password.encode('utf-8')):
                    access_token = jwt.encode({'id':user.account, 'exp': datetime.utcnow() + timedelta(hours=24)}, SECRET_KEY, ALGORITHM).decode('utf-8')

                    return JsonResponse({"ACCESS_TOKEN":access_token}, status=201)

                return JsonResponse({"message": "INVALID_USER"}, status = 401)

            return JsonResponse({"message":"INVALID_USER"}, status=401)

        except KeyError:
            return JsonResponse({"message":"KEY_ERROR"}, status=400)

2. JWT 인증 데코레이터

웹사이트를 이용하다보면 장바구니 등 회원가입을 한 사용자만이 이용할 수 있는 기능들이 있다. 이때 로그인 시 발급된 토큰을 이용하여 해당 사용자가 로그인이 되어 있는지 또는 유료 사용자인지 등을 구분하게 된다.
데코레이터를 이용하면 어떠한 기능을 수행하기 전 로그인을 했는지 검사하기 위한 구문을 매번 코딩하지 않고 간단히 표현할 수 있다. 데코레이터는 @함수명으로 나타낸다.

JWT 인증 데코레이터 구현을 위해 USER apputils.py라는 파일을 생성하고 중첩함수로 데코레이터(@signin_decorator)를 만들었다. 데코레이터를 구현하는 동안 JWT와 중첩함수에 대한 개념을 이해할 수 있어서 도움이 많이 되고 유익한 시간이었다.

참고로 데코레이터를 이용하기 위해선 from .utils import signin_decorator으로 import를 한 후 사용하면 된다.

1) 데코레이터 구현

구현사항

  • 에러 처리
  • HTTP 요청에서 Authorization이 없으면(None인 경우) INVALID_LOGIN 반환
  • token의 만료시간이 끝난 경우 EXPIRED_TOKEN 반환
  • token 복호화 에러 발생 시 INVALID_TOKEN 반환
  • 사용자가 존재하지 않는 경우 INVALID_USER 반환
  • 유효하지 않은 token일때 NEED_LOGIN 반환
  • HTTP 요청에서 authorization header 값 get
  • JWT 암호화 시 사용한 SECRET_KEY와 ALGORITHM을 이요하여 token을 복호화한 후 token_payload 변수에 할당
  • 복호화한 JWT의 사용자 id를 변수(user)에 할당
  • 요청된 사용자와 실제 사용자가 일치하는지 비교 (request.user = user는 decorator를 이용해 jwt 인증을 할 때 마다 사용될 중요한 정보이다)
import json, jwt

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

from my_settings             import SECRET_KEY, ALGORITHM
from .models                 import User

    
def signin_decorator(func):
    def wrapper(self, request, *args, **kwargs): 

        access_token    = request.headers.get("Authorization", None)  

        if "Authorization" ==  None:
            return JsonResponse({"message":"INVALID_LOGIN"}, status=401)
        
        try:
            token_payload   = jwt.decode(access_token, SECRET_KEY, ALGORITHM)
            user            = User.objects.get(account=token_payload['id']) 
            request.user    = user

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

        except jwt.ExpiredSignatureError:
            return JsonResponse({"message":"EXPIRED_TOKEN"}, status=401) 

        except jwt.DecodeError: 
            return JsonResponse({"message":"INVALID_TOKEN"}, status=401) 

        except User.DoesNotExist:
            return JsonResponse({"message":"INVALID_USER"}, status=401) 
       
        except jwt.InvalidTokenError:
            return JsonResponse({"message":"NEED_LOGIN"}, status=401)

    return wrapper

2) 데코레이터 테스트

작성한 데코레이터가 정상적으로 작동하는지 확인을 하고 싶은데 내가 맡은 부분은 데코레이터를 붙일 부분이 없어 테스트view를 작성해보았다.

views.py

class TestDecoratorView(View):
     @signin_decorator
     def get(self, request):
     
     	print(f'user:{request.user}')
        return JsonResponse({"message":"success"})

urls.py

from django.urls import path
from .views import SignupView, SigninView, TestDecoratorView
 
urlpatterns = [
	path('/signup', SignupView.as_view()),
	path('/signin', SigninView.as_view()),
	path('/decoratortest', TestDecoratorView.as_view())
]

위와 같이 작성 후 통신을 하면 아래와 같이 데코레이터 작동 여부를 확인할 수 있다 :)


profile
What you think, you become. What you feel, you attract. What you imagine, you create.

0개의 댓글