TIL 21 Django 회원가입&로그인 (2)_add : 로그인 기능 및 암호화

CastleQ·2021년 6월 6일
1

django

목록 보기
5/5
post-thumbnail

필자는 백엔드 개발자 지망생으로 장고를 이용한 회원가입 로그인 기능을 간단히 구현해 보았습니다.

지금 작성하는 포스팅은 인증&인가, JWT, Bcrypt에 대한 이해가 있다고 작성하도록 하겠습니다 !

혹시나 간단한 이해가 필요하시다면
TIL 17 인증(Authentication) & 인가(Authorization) 기초개념
TIL 18 bcrypt(비크립트)
TIL 19 JWT(JSON Web Token)
위의 자료를 참고해 주세요 !

이전 포스팅에서 회원가입 기능을 구현해 보았고 이 포스팅에서는 로그인 기능과 함께 password의 암호화 그리고 프론트단에 token을 보내보는 기능까지 한번 구현해 보도록 하겠다.

user/views.py import

import jwt, bcrypt, json, re

from django.views         import View
from django.http          import JsonResponse
from django.db.models     import Q

from westagram.settings   import SECRET_KEY
from .models              import User

이전 포스팅에는 없었던 jwt,bcrypt를 import해왔고
tokensettings.pySECRET_KEY를 전달해줘야 하므로 import해왔다.

views.py_SignupView Class 암호화 (회원가입 암호화)

class SignupView(View):
    def post(self, request):

        try:
            user_email            = "(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
            user_phone_number     = '^[0-9]{3}-[0-9]{4}-[0-9]{4}$'
            user_password         = '[A-Za-z0-9@#$]{8,}'

            data                  = json.loads(request.body)
            
            # email과 password의 값이 들어있지 않은 경우
            # email과 password의 KEY값이 일치하지 않을 경우 keyerror 리턴
            if '' == data['email']:
                return JsonResponse({'message': 'INVALID_EMAIL'}, status=400)

            if '' == data['password']:
                return JsonResponse({'message': 'INVALID_PASSWORD'}, status=400)

            # email과 password, phone_number가 정규표현식에 부합하지 않는 경우
            if not re.match(user_email, data['email']):
                return JsonResponse({'message': 'INVALID_EMAIL'}, status=400)

            if not re.match(user_phone_number, data['phone_number']):
                return JsonResponse({'message': 'INVALID_PHONE_NUMBER'}, status=400)

            if not re.match(user_password, data['password']):
                return JsonResponse({'message': 'INVALID_PASSWORD'}, status=400)

            # 중복검사
            if User.objects.filter(
                Q(email=data['email'])|
                Q(phone_number=data['phone_number'])|
                Q(nickname=data['nickname'])).exists():
                return JsonResponse({'MESSAGE' : 'OVERLAP_ERROR'}, status=400)

            # password 암호화
            password        = data['password'].encode('utf-8')
            password_bcrypt = bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8')

            User.objects.create(
                email        = data['email'],
                password     = password_bcrypt,
                phone_number = data['phone_number'],
                nickname     = data['nickname'],
            )

            return JsonResponse({'message': 'SUCCESS'}, status=201)

        except KeyError:
            return JsonResponse({"message": "KEY_ERROR"}, status=400)

기존의 SignUpView에서는 클라이언트 에게서 받은 password를 그대로 DB에 넣었다면
위의 코드에서는 암호화 해서 저장한다.

그러기 위해서는 DB에 값을 저장하기 전에 비밀번호를 암호화 해야하는데 사용자에게 받은 비밀번호를 바이트 타입으로 인코딩해주고, 인코딩한 값을 Hash 값으로 암호화해 준다.

DB에 값을 저장할 때는 다시 디코딩하여 문자열로 저장한다.

SigninView Class

로그인 기능을 구현해 보았다, 처음에 단순히 생각하기에는 로그인은 사용자가 요청을 하면 로그인 되었다고 값을 보내주면 되지않나? 싶어서 get메소드를 사용하려고 했지만 실제로 짜기 시작해보니 사용자가 입력도 해야되고 입력한 값이 DB에 들어있는지 확인 및 로그인에 성공했다는 메시지도 보내줘야 하기 때문에 post메소드를 사용하기로 했다.

lass SigninView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)

            if not User.objects.filter(email=data['email']).exists():
                return JsonResponse({"message": "INVALID_USER"}, status=401)
            user = User.objects.get(email=data['email'])

            # 비밀번호 확인
            if not bcrypt.checkpw(data['password'].encode('utf-8'), user.password.encode('utf-8')):
                return JsonResponse({"message": "INVALID_USER"},status=401)

            # 토큰 발행
            token = jwt.encode({'eamil' : data['email']}, SECRET_KEY, algorithm="HS256")
            return JsonResponse({"token": token}, status=200)
            

        except KeyError:
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)

위 코드의 로직은 사용자가 보내온 요청을 local의 DB에 값이 들어있는지 확인 한 후에
user라는 변수를 선언해 필터링된 사용자의 정보를 담는다.

비밀번호 확인 (bcrypt)

그 후, checkpw()함수를 사용하여 비밀번호를 비교한다. 사용자가 입력한 값과 데이터에 들어있는 값의 비밀번호를 인코딩하여 bytes화 시킨다음에 비교 후 DB에 있는 값과 입력된 값이 일치한다면 다음 단계의 코드가 실행된다.

token발행 (jwt)

토큰을 사용자에게 전달해 줄 것이다.
비밀번호를 확인하는 code까지 무사히 실행되었다면 jwt를 사용해 사용자의 정보, SECRET_KEY, 어떤 알고리즘을 사용 하였는지를 token 변수에 담아 보내준다.

회고

현업 개발자분들의 시선에서 보면 몇 줄 안 되는 짧은 code이지만 이런 간단한 기능을 구현하면서도 엄청나게 검색을 많이 하고 공식 문서도 많이 찾아봤다. (하지만 잘 이해되지는 않지)
쉽지는 않았지만 오류를 하나하나 수정하면서 뭔가를 구현 했을 때의 기분은 정말 뭐라 말로 표현 할 수가 없다. 열심히 할수록 재미가 있어서 아직은 신기하고 좋아서 살짝 무섭다. 하지만 지금을 기억하며 찬찬히 뜯어보면 할 수 있어 !!!!! 라고 생각하고 앞으로 가보려고 한다.

p.s 저기 나 님아 시간을 좀 밀도 있게 쓰려고 노력해봐라 한 시간이 다 같은 한 시간이 아닌거 너도 잘 알잖아? 파이팅이다.

profile
DONE IS BETTER THAN PERFECT

0개의 댓글