이번에는 이전에 제작했던 회원가입/로그인 앱에 암호화 및 토큰 발행 기능을 추가해보려고 합니다. 이전 제작과정은 아래 글에서 확인하실 수 있습니다.
이전 글에서 bcrypt
와 jwt
사용법을 충분히 확인하셨다는 가정하에 코드 작성을 진행하겠습니다.
먼저 비밀번호 암호화와 토큰 발행에 활용되는 라이브러리와 모듈을 임포트 해옵니다.
import json
import bcrypt # 암호화에 사용
import jwt # 토큰 발행에 사용
from westa.settings import SECRET_KEY # 토큰 발행에 사용할 secret key
from .models import Account
from django.views import View
from django.http import JsonResponse, HttpResponse
이전에 작성했던 SignUpView
에서는 사용자에게서 받은 비밀번호를 그대로 DB에 넣었다면, 이번에는 해당 비밀번호를 암호화해서 암호화된 형태로 넣으려고 합니다.
그러기 위해서는 DB에 값을 집어넣기 전에 비밀번호를 암호화 해야합니다. 사용자에게 받은 비밀번호를 바이트
타입으로 인코딩해주고, 인코딩한 값을 Hash 값으로 암호화해줍니다.
DB에 이 값을 집어넣을때는 다시 유니코드 문자열로 변환해줍니다. 그리고 나서 변환해준 값을 DB에 저장하면 끝!
class SignUpView(View):
def post(self, request):
data = json.loads(request.body)
try:
if Account.objects.filter(email = data['email']).exists(): # 존재하는 이메일인지 확인
return HttpResponse(status=400)
#==== 비밀번호 암호화====#
password = data['password'].encode('utf-8') # 입력된 패스워드를 바이트 형태로 인코딩
password_crypt = bcrypt.hashpw(password, bcrypt.gensalt()) # 암호화된 비밀번호 생성
password_crypt = password_crypt.decode('utf-8') # DB에 저장할 수 있는 유니코드 문자열 형태로 디코딩
#====================#
Account(
email = data['email'],
password = password_crypt # 암호화된 비밀번호를 DB에 저장
).save()
return HttpResponse(status=200)
except KeyError:
return JsonResponse({"message":"INVALID_KEYS"}, status = 400)
로그인 뷰에서는 사용자가 입력한 비밀번호가, DB에 있는 사용자의 이메일과 매칭되는 비밀번호와 일치하는지 확인을 해야합니다. 만약 일치한다면 토큰을 발행해서 HTTP 응답에 같이 넘겨주면 됩니다.
여기서 비밀번호가 일치하는지 확인한다는 말은, 비밀번호 그 자체를 비교한다는게 뜻은 아니고 [이전에 저장해놓은 암호화된 비밀번호]
와 [사용자가 이번에 입력한 비밀번호]
를 bcrypt.check
메서드로 비교해서 [True or False]
결과값을 주는겁니다.
비밀번호 확인 후 토큰이 생성되면, Json Response
로 토큰을 응답할 수 있도록 유니코드 문자열로 디코딩해 값을 전달해줍니다.
*토큰에는 유저의 이메일 정보를 넣어두었습니다.
class SignInView(View):
def post(self, request):
data = json.loads(request.body)
try:
if Account.objects.filter(email = data['email']).exists() :
user = Account.objects.get(email = data['email'])
#---------비밀번호 확인--------#
# 사용자가 입력한 비밀번호를 인코딩하고, 사용자의 이메일과 매칭되는 DB의 비밀번호를 찾아와서 인코딩. 이 두 값을 bcrypt.checkpw로 비교하면 됨
if bcrypt.checkpw(data['password'].encode('utf-8'), user.password.encode('utf-8')) :
#----------토큰 발행----------#
token = jwt.encode({'email' : data['email']}, SECRET_KEY, algorithm = "HS256")
token = token.decode('utf-8') # 유니코드 문자열로 디코딩
#-----------------------------#
return JsonResponse({"token" : token }, status=200) # 토큰을 담아서 응답
else :
return HttpResponse(status=401)
return HttpResponse(status=400)
except KeyError:
return JsonResponse({"message":"INVALID_KEYS"}, status = 400)
이제 HTTP로 응답해 준 토큰을 프론트엔드 개발자가 사용자의 HTTP Request header
에 잘 넣어줬다고 가정하고, 해당 토큰이 현재 페이지에서 활동할 수 있도록 인가된 토큰인지 확인해주는 뷰를 만들어보겠습니다.
먼저 TokenCheckView
에서는 요청으로 넘어온 토큰 정보를 기존에 토큰을 발행할 때 적용했던 SECRET_KEY
와 알고리즘 정보를 넣어서 디코딩 해줍니다.
디코딩을 하면 (요청으로 넘어온 토큰이 정상 발행된 토큰이라면) 토큰을 발행할 때 넣었던 유저 정보가 반환됩니다. 이 유저 정보가 DB에 있는 정보와 일치한다면 status=200
을 응답합니다.
정보가 일치하지 않는다면, 인가되지 않은 사용자이기 때문에 status=403
을 응답해줍니다.
class TokenCheckView(View):
def post(self,request):
data = json.loads(request.body)
user_token_info = jwt.decode(data['token'], SECRET_KEY, algorithm = 'HS256')
if Account.objects.filter(email=user_token_info['email']).exists() :
return HttpResponse(status=200)
return HttpResponse(status=403)
이제 끝났습니다! 전체 코드는 아래에서 보실 수 있습니다.
import json
import bcrypt
import jwt
from westa.settings import SECRET_KEY
from .models import Account
from django.views import View
from django.http import JsonResponse, HttpResponse
class SignUpView(View):
def post(self, request):
data = json.loads(request.body)
try:
if Account.objects.filter(email = data['email']).exists(): # 존재하는 이메일인지 확인
return HttpResponse(status=400)
#== 비밀번호 암호화==#
password = data['password'].encode('utf-8') # 입력된 패스워드를 바이트 형태로 인코딩
password_crypt = bcrypt.hashpw(password, bcrypt.gensalt()) # 암호화된 비밀번호 생성
password_crypt = password_crypt.decode('utf-8') # DB에 저장할 수 있는 유니코드 문자열 형태로 디코딩
#====================#
Account(
email = data['email'],
password = password_crypt # 암호화된 비밀번호를 DB에 저장
).save()
return HttpResponse(status=200)
except KeyError:
return JsonResponse({"message":"INVALID_KEYS"}, status = 400)
class SignInView(View):
def post(self, request):
data = json.loads(request.body)
try:
if Account.objects.filter(email = data['email']).exists() :
user = Account.objects.get(email = data['email'])
if bcrypt.checkpw(data['password'].encode('utf-8'), user.password.encode('utf-8')) :
#----------토큰 발행----------#
token = jwt.encode({'email' : data['email']}, SECRET_KEY, algorithm = "HS256")
token = token.decode('utf-8') # 유니코드 문자열로 디코딩
#-----------------------------#
return JsonResponse({"token" : token }, status=200)
else :
return HttpResponse(status=401)
return HttpResponse(status=400)
except KeyError:
return JsonResponse({"message":"INVALID_KEYS"}, status = 400)
class TokenCheckView(View):
def post(self,request):
data = json.loads(request.body)
user_token_info = jwt.decode(data['token'], SECRET_KEY, algorithm = 'HS256')
if Account.objects.filter(email=user_token_info['email']).exists() :
return HttpResponse(status=200)
return HttpResponse(status=403)