지난 번 인증, 인가 실습에서 조금 더 발전시켰다. 모두 views 내에서 이루어졌던 인가 과정을 분리해 데코레이터로 만들어주었고, 뭉뚱그려 출력되던 에러 메시지도 에러 케이스를 세분화해 에러를 특정할 수 있도록 수정했다.
유효한 토큰인지 검증하는 데코레이터
import jwt
from django.http import JsonResponse
from .models import Users
def auth(func):
def wrapper(self, request, **kwargs):
# request 헤더에서 토큰 가져오기.
auth_token = request.headers.get("Authorization", None)
# 토큰 값이 아예 안 들어왔을 때 401 코드 처리 및 메시지 출력
if auth_token == None:
return JsonResponse({'message':'Enter the token.'}, status=401)
try:
# 받은 토큰 디코딩해서 user id 정보 출력하기
payload = jwt.decode(auth_token, 'secret')
# 위에서 디코딩한 user id 숫자만 추출해서 DB에 있는지 대조
if Users.objects.get(id=payload['id']):
# id 숫자를 user에 할당
user = Users.objects.get(id=payload['id'])
# 데이터 테이블에 열 이름과 값 동적 추가
request.user = user
return func(self, request, **kwargs)
# signiture 부분이 잘못됐을 때 401 코드 처리 및 메시지 출력
except jwt.InvalidSignatureError:
return JsonResponse({'message':'Invalid token. Check the suffix.'}, status=401)
# header 부분이 잘못됐을 때 401 코드 처리 및 메시지 출력
except jwt.DecodeError:
return JsonResponse({'message':'Invalid token. Check the prefix.'}, status=401)
return wrapper
코멘트 views.py 파일에 데코레이터 붙이기
import json
from django.views import View
from django.http import JsonResponse, HttpResponse
from .models import Comments
# 데코레이터를 다른 파일에 작성했으므로 임포트 해야함
from users.utils import auth
class CommentView(View):
# auth 데코레이터 장식
@auth
def post(self, request):
try:
# 데이터 테이블에 동적으로 생성해준 데이터를 끌어와서 user에 할당해줌
# print 찍어보면 {'username_id' : 1 } 같은 형식임
user = request.user
# request body로부터 받아올 데이터 (유저 입력값)
data = json.loads(request.body)
# 프론트에서 코멘트가 공백일 때 게시 버튼이 활성화되지 않도록 1차로 막고 백에서 2차로 막아줌
if data['comment'] == '':
return HttpResponse(status=400)
# FK로 끌어온 username에(즉, 데이터 테이블 열 이름은 username_id) 유저 id 숫자만 뽑아서 할당
# comment는 유저가 입력한 값을 request body에서 받아옴
Comments(
username_id = user.id,
comment = data['comment'],
).save()
return JsonResponse({'comment': data['comment']}, status=200)
# 프론트에서 key 이름을 잘못 지정해서 보냈을 때 출력할 에러 코드와 메시지
# as e로 조건 걸어주면 e에 올바른 key 값이 리턴됨
except KeyError as e:
return JsonResponse({'message': e + 'Invalid key. The key name is comment.'})
except:
return JsonResponse({'message':'Something wrong.'}, status=401)
# get 생략
users views.py 예외 조건 세분화
import json
import bcrypt
import jwt
from django.views import View
from django.http import JsonResponse, HttpResponse
from .models import Users
class MainView(View):
def get(self, request):
return JsonResponse({'Welcome to':'Westagram', 'Sign-up':'/users/sign-up', 'Log-in':'/users/log-in'}, status=200)
class SignUpView(View):
def post(self, request):
data = json.loads(request.body)
try:
hased_pw = bcrypt.hashpw(data['password'].encode('utf-8'), bcrypt.gensalt())
decoded_hashed_pw = hased_pw.decode('utf-8')
if data['username'] == '':
return JsonResponse({'message':'username is required.'}, status=401)
elif data['password'] == '':
return JsonResponse({'message':'password is required.'}, status=401)
elif Users.objects.filter(username=data['username']).exists():
return JsonResponse({'message':'username already exists.'}, status=409)
if not Users.objects.filter(username=data['username']).exists():
Users(
username = data['username'],
password = decoded_hashed_pw
).save()
return JsonResponse({'message':'WELCOME, ' + data['username']}, status=200)
except IntegrityError:
return JsonResponse({'message':'username already exists.'}, status=409)
# 프론트에서 key 이름을 잘못 지정해서 보냈을 때 출력할 에러 코드와 메시지
# as e로 조건 걸어주면 e에 올바른 key 값이 리턴됨
except KeyError as e:
return JsonResponse({'message': str(e) + ' is right key name. The key names are username and password.'}, status=400)
except:
return JsonResponse({'message':'Something wrong.'}, status=401)
# get 생략
class LogInView(View):
def post(self, request):
data = json.loads(request.body)
try:
if Users.objects.filter(username=data['username']).exists():
user_id = Users.objects.get(username=data['username'])
if bcrypt.checkpw(data['password'].encode('utf-8'), user_id.password.encode('utf-8')) == True:
access_token = jwt.encode({'id':user_id.id}, 'secret', algorithm='HS256')
# 토큰 같은 값 리턴 시 부가 메시지 없이 value란에는 value만 넣어서 리턴해야 함
# 그래야 프론트에서 깔끔하게 처리 가능
return JsonResponse({'message':'WELCOME BACK, ' + data['username'], 'token' : access_token.decode('utf-8')}, status=200)
else:
return JsonResponse({'message':'Wrong password.'}, status=401)
else:
return JsonResponse({'message':'Wrong username.'}, status=401)
# 프론트에서 key 이름을 잘못 지정해서 보냈을 때 출력할 에러 코드와 메시지
# as e로 조건 걸어주면 e에 올바른 key 값이 리턴됨
except KeyError as e:
return JsonResponse({'message': str(e) + ' is right key name. The key names are username and password.'}, status=400)
# get 생략
어려웠던 점
에러 케이스를 세분화하기 위해 정확히 어떤 에러가 뜨는지 알아야 했다. 이때 except Exceptions as e
를 아주 유용하게 잘 써먹었다. 하지만 유독 토큰 값이 아예 입력되지 않거나, 틀린 토큰이 입력됐을 때 뜨는 에러를 잡아내기가 어려웠는데, 알고보니 except
를 데코레이터에서 걸어줬어야 했다. 데코레이터 단에서 이미 토큰 검증 등의 토큰과 관련한 작업을 마치기 때문이다. 데코레이터를 왜 만드는지 잘 생각해보면 어떤 기능이 들어가야할지 알 수 있을 것이다.
그리고 데이터 테이블에 동적으로 값을 추가해주는 개념이 잘 안 잡힌다. 코드를 보면 읽을 수는 있는데, 내가 혼자 적으려면 아직 컨닝이 필요한 부분…