Django | 인스타그램 클론 코딩(3) - 회원가입/로그인 인증 추가

Sua·2021년 2월 8일
0

Django

목록 보기
10/23
post-thumbnail

👩‍🔧 bcrypt와 JWT로 회원가입/로그인 인증 추가

이전 시간에는 장고로 인스타그램 회원가입 기능과 로그인을 구현하였는데요, 이번에는 bcrypt와 JWT라는 라이브러리를 사용해서 비밀번호를 암호화하고 access token을 발급하는 등 인증 과정을 구현하도록 하겠습니다.

bcrypt와 JWT의 기본적인 사용법은 이 두 개의 포스팅(인증과 bcrypt, 인가와 JWT)에서 다루었으니 참고하시길 바랍니다.

🗝 회원가입 시 비밀번호 암호화해서 저장(bcrypt)

# 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하는 이유?

그런데 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/gcwNvkpjj5HnpVpiktoUmwCWb 없이 저장된 것을 볼 수 있습니다.

💰 로그인 시 토큰 발급(JWT)

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"  

다음 시간에는 인스타그램 게시물 등록 기능을 구현하도록 하겠습니다.

profile
Leave your comfort zone

0개의 댓글