[TIL / Django] Westagram 4: 비밀번호 암호화 및 JWT 토큰 발행(bcrypt , JWT)

나른한 개발자·2022년 1월 20일
0

studylog

목록 보기
32/45
post-custom-banner

이번에는 인증/인가 시간에 배운 것을 토대로 비밀번호를 암호화하고

구현사항

  • 회원가입 코드에 비밀번호 암호화 추가
  • 로그인 성공 시 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 라이브러리를 사용하기 위해선 pip으로 먼저 설치해주어야 한다.
  • bcrypt란? 단방향 암호화 라이브러리
  • hashpw()로 암호화할 패스워드와 솔트값을 인자로 받는데, 이때 패스워드는 Byte형태로 인코딩 되어야 한다.
  • 데이터베이스의 패스워드 필드는 CharField이므로 해시된 패스워드를 저장할때는 다시 디코딩한다.

bcrypt:
SHA-256과 같은 단방향 해시함수는 한번 암호화하면 복호화할 수 없을 뿐만 아니라 같은 문자열을 넣었을 때 항상 같은 해시값이 나오기 때문에 salt값과 같이 해시해주어야한다. salt라는 이름처럼 패스워드에 소금을 치듯이 무언가를 더해서 해시해주는 것이다. 이 무작위 값을 패스워드와 함께 해시한 후 패스워드가 맞는지 대조할 때 전달받은 패스워드를 저장된 솔트와 해시하여 DB에 저장된 값과 같은지를 판단한다. 이때 솔트를 저장할 데이터 필드가 따로 있어야하지만 bcrypt 라이브러리를 사용하면 필드에 따로 저장할 필요없이 솔트값을 함께 저장할 수 있다.

코드 - JWT 토큰 발행

...생략
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)
        
  • 입력된 비밀번호가 유효한지를 알아보기 위해서는 checkpw()를 사용해야한다. 입력받은 비밀번호, DB에 저장되어 있는 비밀번호를 인코딩하여 인자로 전달한다.

피드백

  • 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']
profile
Start fast to fail fast
post-custom-banner

0개의 댓글