로그인 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)
웹사이트를 이용하다보면 장바구니 등 회원가입을 한 사용자만이 이용할 수 있는 기능들이 있다. 이때 로그인 시 발급된 토큰을 이용하여 해당 사용자가 로그인이 되어 있는지 또는 유료 사용자인지 등을 구분하게 된다.
데코레이터를 이용하면 어떠한 기능을 수행하기 전 로그인을 했는지 검사하기 위한 구문을 매번 코딩하지 않고 간단히 표현할 수 있다. 데코레이터는 @함수명
으로 나타낸다.
JWT 인증 데코레이터 구현을 위해 USER app
에 utils.py
라는 파일을 생성하고 중첩함수로 데코레이터(@signin_decorator
)를 만들었다. 데코레이터를 구현하는 동안 JWT와 중첩함수에 대한 개념을 이해할 수 있어서 도움이 많이 되고 유익한 시간이었다.
참고로 데코레이터를 이용하기 위해선 from .utils import signin_decorator
으로 import를 한 후 사용하면 된다.
구현사항
- 에러 처리
- HTTP 요청에서 Authorization이 없으면(
None
인 경우)INVALID_LOGIN
반환- token의 만료시간이 끝난 경우
EXPIRED_TOKEN
반환- token 복호화 에러 발생 시
INVALID_TOKEN
반환- 사용자가 존재하지 않는 경우
INVALID_USER
반환- 유효하지 않은 token일때
NEED_LOGIN
반환
authorization
header 값 gettoken_payload
변수에 할당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
작성한 데코레이터가 정상적으로 작동하는지 확인을 하고 싶은데 내가 맡은 부분은 데코레이터를 붙일 부분이 없어 테스트view를 작성해보았다.
class TestDecoratorView(View):
@signin_decorator
def get(self, request):
print(f'user:{request.user}')
return JsonResponse({"message":"success"})
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())
]
위와 같이 작성 후 통신을 하면 아래와 같이 데코레이터 작동 여부를 확인할 수 있다 :)