인스타그램 로그인 (회원가입, 로그인 기능 구현)

김루트·2021년 4월 11일
0

Python 학습

목록 보기
5/6
from django.db    import models
from .validations import validate_email, validate_password, validate_phone

class User(models.Model):
    name         = models.CharField(max_length=45, default='enter name')
    email        = models.EmailField(max_length=100, validators=[validate_email], unique=True)
    password     = models.CharField(max_length=1000, validators=[validate_password])
    phone_number = models.CharField(max_length=20, validators=[validate_phone], default='enter phone number')
    nickname     = models.CharField(max_length=20, default='enter nickname')

    class Meta:
        db_table = "users"
import json,traceback
import bcrypt
import jwt

from django.http                import JsonResponse
from django.core.exceptions     import ValidationError
from django.views               import View
from django.db.models           import Q

from users.models               import User
from .validations               import validate_email, validate_phone, validate_password
from my_settings                import SECRET_KEY

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

        try:
            data = json.loads(request.body)

            if not data['email'] or not data['password']:
                return JsonResponse({'message': 'NO_VALUES_'}, status=400)

            if not validate_email(data['email']):
                return JsonResponse({'message': 'USE_VALID_EMAIL'}, status=401)

            if not validate_password(data['password']):
                return JsonResponse({'message': 'PASSWORD_TOO_SHORT'}, status=402)

            hashed_password = bcrypt.hashpw(data['password'].encode('UTF-8'), bcrypt.gensalt()).decode()

            User.objects.create(
                email        = data['email'],
                password     = hashed_password,
                phone_number = data['phone_number'],
                name         = data['name'],
                nickname     = data['nickname'],
            )
            return JsonResponse({'message': 'SUCCESS',"user_email":data['email']}, status=201)

        except ValidationError as VE:
            trace_back = traceback.format_exc()

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

class LogInView(View):
    def post(self,request):
        data = json.loads(request.body)

        try:

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

            user_email = User.objects.get(email=data['email'])
            if not bcrypt.checkpw(data['password'].encode('utf-8'), user_email.password.encode('utf-8')):
                return JsonResponse({'message': 'INCORRECT_PASSWORD'}, status=402)

            access_token = jwt.encode({'id': user_email.id}, SECRET_KEY['secret'], algorithm='HS256')
            return JsonResponse({'token': access_token,'message':'SUCCESS','user_email':data['email']},status=200)

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


전체적인 로직은 위와 같다. 본래 다양한 조합의 로그인을 구현하기 위해models.py에는 다양한 필드가 있지만, 여기서는 email과 패스워드만을 이용한 로그인만으로 범위를 좁혔다.

지적 사항 및 주의점

  1. models.py에서 CharField로 해결할 수 있는 부분이라면 가급적 TextField를 쓰지 않다는 지적이 있었다. 그러나 이와 관련된 스택오버플로우 스레드에 따르면, 성능면에서 유의미한 차이는 없다고 한다. 다만, 최대 길이를 명시해주는 것이 좋은 경우라면 CharField를 사용하는 것이 적합하다고 한다.

  2. elif, else의 사용은 가급적이면 지양하는 것이 좋다. 이런 식으로 로직을 처리할 경우, 생각하기도 복잡하고 간과하게 되는 경우의 수가 발생할 수도 있다. 그래서 좋은 방법은 "안되는 경우의 수", 즉 에러가 발생하는 경우의 수를 생각해서 예외 처리를 하거나, if를 활용하여 각 상황에 맞는 에러 메세지를 호출하는 것이 좋다. 요컨대, "positive" 방식으로 에러가 발생하지 않는 경우를 일일이 열거하기 보다는, "negative"한 접근법을 통해 에러가 발생하는 경우만을 열거하고 처리하는 것이 편리하고 확실하다.

  3. 패스워드를 암호화할 때, encode 된 상태로 데이터베이스에 바로 저장한다면, 암호화된 패스워드 앞에 'b'표시가 붙는 것을 확인할 수 있다. 이는 암호화된 패스워드가 바이트 형태로 저장되었음을 의미한다. 만약 DB에 암호화된 패스워드를 저장할 때 decode를 통해 이를 문자열로 변환하지 않는다면, 로그인 시에 bcrypt.checkpw 부분에서 문제가 발생할 수 있다. checkpw 과정에서, 데이터베이스에 저장된 암호와 사용자가 입력한 암호를 encode하여 비교하는데, 만약 DB에 바이트 형식으로 b가 붙은 채로 저장이 되어있다면, checkpw과정에서 이 b까지 스트링으로 생각하여 포함시켜 암호화하기 때문에, 비밀번호가 일치하지 않는 문제가 발생한다. 따라서, 회원가입 시 데이터베이스에 패스워드를 저장할 때는 decode한 상태여야만 한다.

4.access_token의 "SECRET_KEY"는 내가 직접 만든 'my_settings.py' 파일에서 불러온다. 이는 딕셔너리의 형태로 저장되어있다.

SECRET_KEY = {'secret': '*-5684@z$@$sqrilu5ss)cerm%id3xw^_418sfuyuo4=6%)vbe'}

그렇기 때문에 SECRET_KEY['secret'] 와 같은 형식으로 해당 문자열(값)을 불러왔지만, 사실 저 위치에는 다른 임의의 문자열을 대입해도 무방하다.

profile
반갑습니다.

0개의 댓글