이번에는 인증/인가 시간에 배운 것을 토대로 비밀번호를 암호화하고
- 회원가입 코드에 비밀번호 암호화 추가
- 로그인 성공 시 JWT 발행
import json, re, brcypt, jwt
from django.http import JsonResponse
from django.views import View
from users.models import User
class SignUpView(View):
def post(self, request):
data = json.loads(request.body)
try:
user_email = data["email"]
user_password = data["password"]
user_phone_number = data["phone_number"]
REGEX_EMAIL = '^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
REGEX_PASSWORD = '^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&.]{8,}$'
REGEX_PHONE_NUMBER = '\d{3}-\d{3,4}-\d{4}'
if not re.match(REGEX_EMAIL, user_email):
return JsonResponse({"message": "INVALID_EMAIL"}, status=400)
if not re.match(REGEX_PASSWORD, user_password):
return JsonResponse({"message": "INVALID_PASSWORD"}, status=400)
if not re.match(REGEX_PHONE_NUMBER, user_phone_number):
return JsonResponse({"message": "INVALID_PHONE_NUMBER"}, status=400)
if not User.objects.filter(email=user_email).exists:
return JsonResponse({"message": "EMAIL_ALREADY_EXISTS"}, status=400)
encoded_password = user_password.encode('utf-8')
hashed_password = bcrypt.hashpw(decoded_password, bcrypt.gensalt())
decoded_hashed_password = hashed_password.decode('utf-8')
User(
name=data["name"],
email=user_email,
password=decoded_hashed_password,
phone_number=user_phone_number
)
return JsonResponse({"message": "SUCCESS"}, status=201)
except KeyError:
return JsonResponse({"message": "KEY_ERROR"}, status=401)
bcrypt:
SHA-256과 같은 단방향 해시함수는 한번 암호화하면 복호화할 수 없을 뿐만 아니라 같은 문자열을 넣었을 때 항상 같은 해시값이 나오기 때문에 salt값과 같이 해시해주어야한다. salt라는 이름처럼 패스워드에 소금을 치듯이 무언가를 더해서 해시해주는 것이다. 이 무작위 값을 패스워드와 함께 해시한 후 패스워드가 맞는지 대조할 때 전달받은 패스워드를 저장된 솔트와 해시하여 DB에 저장된 값과 같은지를 판단한다. 이때 솔트를 저장할 데이터 필드가 따로 있어야하지만 bcrypt 라이브러리를 사용하면 필드에 따로 저장할 필요없이 솔트값을 함께 저장할 수 있다.
...생략
from django.conf import settings
class SignInView(View):
data = json.loads(request.body)
try:
user_email = data["email"]
user_password = data["password"].encode('utf-8')
user = User.objects.get(email=user_email)
if not bcrypt.checkpw(user_password, user.password.encode('utf-8')):
return JsonResponse({"message":"INVALID_USER"}, status=400)
access_token = jwt.encode({"user_id":user.id}, settings.SECRET_KEY, settings.ALGORITHM)
return JsonResponse({"access_token": access_token}, status=200)
except User.DoesNotExist:
return JsonResponse({"message": "INVALID_USER"}, status=400
except KeyError:
return JsonResponse({"message":"KEY_ERROR"}, status=401)
DoesNotExist 에러
-> jwt 발급을 구현하기 전 코드에서는 if
문과 filter().exists()
를 사용하여 계정 존재 여부를 검사했었다. 하지만 이 코드에서는 어차피 User의 객체가 필요하기 때문에 get을 이용하여 객체를 가져온 뒤, 일치하는 객체가 없다면 except문에서 DoesNotExist 에러를 일으키도록 하였다. 만약 일치하는 객체가 1개 이상이라면 Multiple Objects 에러도 잡을 수 있지만 DB상에서 email 필드는 unique=True
로 설정해놓았기 때문에 이 에러는 애초에 발생할 수 가 없다.
my_settings 환경변수 관리
-> my_settings는 환경 변수 관리를 하기 위한 파일로, views.py 코드 내에서 직접 import하여 사용하게 되면 의존성이 생긴다. 예를 들어, my_settings 파일 내에서 관리하고 있던 환경 변수 말고 내 컴퓨터 자체 설정 파일의 환경변수로 변환하고 싶은 경우 코드 내의 관련부분을 모두 수정해주어야 한다.
따라서 가장 좋은 방법은 컴퓨터 내의 설정 파일에 환경변수를 설정한 뒤 settings.py에 정의를 해주어 사용하면 된다. (환경 변수 관리를 한 곳에서 할 수 있도록) 그러고 난 다음 views.py에서 from django.conf import settings
를 하여 환경 변수 사용한다.
my_settings.py 를 그대로 사용하고 싶다면 views.py 내의 import 부분만 수정하면 된다.
> vi ~/.zshrc
# ~/.zshrc
export SECRET_KEY= '시크릿 키'
export ALGORITHM='HS256'
# settings.py
import os
SECRET_KEY = os.environ.get['SECRET_KEY']
ALGORITHM = os.environ.get['ALGORITHM']