이전 시간에는 장고로 인스타그램 회원가입 기능과 로그인을 구현하였는데요, 이번에는 bcrypt와 JWT라는 라이브러리를 사용해서 비밀번호를 암호화하고 access token을 발급하는 등 인증 과정을 구현하도록 하겠습니다.
bcrypt와 JWT의 기본적인 사용법은 이 두 개의 포스팅(인증과 bcrypt, 인가와 JWT)에서 다루었으니 참고하시길 바랍니다.
# user/views.py
import json
import re
import bcrypt --> 추가
import jwt --> 추가
from json.decoder import JSONDecodeError
from django.http import JsonResponse
from django.views import View
from django.db.models import Q
from my_settings import SECRET, ALGORITHM --> 추가
from user.models import User
bcrypt와 pyjwt를 설치하고 라이브러리를 import합니다.
my_settings.py에 들어 있는 SECRET
, ALGORITHM
정보를 import합니다. JWT 토큰 발행 시 필요합니다.
(SECRET
, ALGORITHM
정보는 유출되면 안 되기 때문에 따로 담아두었습니다. my_settings.py의 위치는 manage.py과 같은 곳에 저장하면 됩니다.)
class SingUpView(View):
def post(self, request):
try:
data = json.loads(request.body)
email = data.get('email', None)
mobile_number = data.get('mobile_number', None)
full_name = data.get('full_name', None)
username = data.get('username', None)
password = data.get('password', None)
...(생략)
User.objects.create(
email = email,
mobile_number = mobile_number,
full_name = full_name,
username = username,
password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
)
return JsonResponse({'message':'SUCCESS'}, status=201)
except JSONDecodeError:
return JsonResponse({'message':'JSON_DECODE_ERROR'}, status=400)
원래는 password = password
와 같이 비밀번호를 평문으로 저장했는데,
password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
와 같이 해싱해서 저장합니다.
그런데 decode('utf-8')
로 해쉬된 비밀번호를 다시 decode하고 있습니다. 왜 그런 걸까요?
decode를 하지 않았을 때 어떤 일이 일어나는지 알아보도록 합시다.
# user/views.py 수정
password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
# 회원가입
http -v POST 127.0.0.1:8000/user/signup email="sue@gmail.com" username="sue" full_name="sue cho" password="12345678"
# mysql DB에 저장된 정보
+----+---------------+---------------+-----------+----------+-----------------------------------------------------------------+----------------------------+----------------------------+
| id | email | mobile_number | full_name | username | password | created_at | updated_at |
+----+---------------+---------------+-----------+----------+-----------------------------------------------------------------+----------------------------+----------------------------+
| 7 | sue@gmail.com | NULL | sue cho | sue | b'$2b$12$BASggijFK.vHmxrjBiBpQuBW/JBQgYEmc29i7kjhs1DTDxPUS3W8.' | 2021-02-09 01:51:09.097403 | 2021-02-09 01:51:09.097428 |
+----+---------------+---------------+-----------+----------+-----------------------------------------------------------------+----------------------------+----------------------------+
password
가 bytes 타입으로 b'$2b$12$BASggijFK.vHmxrjBiBpQuBW/JBQgYEmc29i7kjhs1DTDxPUS3W8.'
로 저장된 것을 확인할 수 있습니다.
문제는 맨 앞의 b
문자입니다.b
는 bytes 타입이라는 것을 알려주기 위한 것인데, DB에 저장될 때는 b
또한 비밀번호로 인식한다는 겁니다.
b
를 없애고 데이터베이스에 저장하기 위해서 decode 과정을 거치면 됩니다.
# user/views.py decode 버전
password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
# 회원가입
http -v POST 127.0.0.1:8000/user/signup email="esther@gmail.com" username="esther" full_name="esther kim" password="12345678"
# mysql DB에 저장된 정보
+----+------------------+---------------+------------+----------+--------------------------------------------------------------+----------------------------+----------------------------+
| id | email | mobile_number | full_name | username | password | created_at | updated_at |
+----+------------------+---------------+------------+----------+--------------------------------------------------------------+----------------------------+----------------------------+
| 8 | esther@gmail.com | NULL | esther kim | esther | $2b$12$U9ZlwcrXRviIH3f.4RELL.VT.782/gcwNvkpjj5HnpVpiktoUmwCW | 2021-02-09 02:02:45.591659 | 2021-02-09 02:02:45.591688 |
+----+------------------+---------------+------------+----------+--------------------------------------------------------------+----------------------------+----------------------------+
password가 $2b$12$U9ZlwcrXRviIH3f.4RELL.VT.782/gcwNvkpjj5HnpVpiktoUmwCW
로 b
없이 저장된 것을 볼 수 있습니다.
class LogInView(View):
def post(self, request):
try:
data = json.loads(request.body)
login_id = data.get('id', None)
password = data.get('password', None)
...(생략)
if not bcrypt.checkpw(password.encode('utf-8'), user.password.encode('utf-8')): [1]
return JsonResponse({'message': 'INVALID_PASSWORD'}, status=401)
access_token = jwt.encode({"id":user.id}, SECRET, algorithm=ALGORITHM) [2]
return JsonResponse({'message': 'SUCCESS', 'Authorization':access_token}, status=200)
except JSONDecodeError:
return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
[1] : bcrypt.checkpw()
메소드로 입력된 비밀번호가 맞는지 확인할 수 있습니다. 입력받은 패스워드, 저장된 암호화된 패스워드를 각각 인자로 받는데, 둘 다 str
타입이므로 encode해줘야 합니다
[2] : 비밀번호가 일치하면 access_token
을 발급하고 JsonResponse에 담아서 보냅니다.
http -v POST 127.0.0.1:8000/user/login id="esther@gmail.com" password="12345678"
다음 시간에는 인스타그램 게시물 등록 기능을 구현하도록 하겠습니다.