Django | 인스타그램 클론 코딩(2) - 로그인

Sua·2021년 2월 8일
0

Django

목록 보기
9/23
post-thumbnail
post-custom-banner

🙋‍♂️ 인스타그램 로그인 기능 구현

저번 포스팅의 회원 가입 기능에 이어 로그인 기능을 구현해보도록 하겠습니다.

마찬가지로 인스타그램 홈페이지에 들어가서 로그인이 어떤 식으로 이루어지는지 파악합니다.

🔎 로그인 기능 분석

  1. 인스타그램에 로그인 할 때에는 사용자 계정으로 phone number, username 또는 email 중 하나가 필요합니다.
  2. 인스타그램에 로그인 할 때에는 비밀번호가 필요합니다.

🖌 Model 작성(생략)

Model은 회원가입 시 작성한 User 클래스를 그대로 활용합니다.

# user/models.py

from django.db import models

class User(models.Model):
    email         = models.CharField(max_length=100, unique=True, null=True)
    mobile_number = models.CharField(max_length=100, unique=True, null=True)
    full_name     = models.CharField(max_length=100)
    username      = models.CharField(max_length=100, unique=True)
    password      = models.CharField(max_length=300)
    created_at    = models.DateTimeField(auto_now_add=True)
    updated_at    = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'users'

💣 View 작성

제가 작성한 로그인 뷰를 보여드리기 전에 어떤 방식으로 작성했는지 설명드리겠습니다. 회원가입 뷰에서 설명한 내용은 생략하도록 하겠습니다.

Part1. id 입력칸은 하나

사용자 계정에는 email, mobile number, username 중 하나의 정보를 입력받을 수 있습니다.

세 가지의 정보를 받을 수 있다보니 각각의 정보를 따로 받아서 각각 변수에 저장하려는 코드를 짤 수 있겠지요. 하지만 이렇게 짜면 문제가 생깁니다.

data = json.loads(request.body)

email = data.get('email', None)
mobile_number = data.get('mobile_number', None)
username = data.get('username', None)
password = data.get('password', None)

바로 입력칸이 하나라는 것입니다. 사용자는 계정을 입력할 때 email인지 username인지 알려주지 않습니다.

만약 사용자가 입력한 정보가 email이라면 프론트에서 확인하여 email이라는 키로 보내줘야 하는데, 쉽지 않을 것 같다는 생각이 확 듭니다.

저는 사용자 계정에 어떤 정보가 입력되든 id라는 키로 받아 login_id라는 변수에 담아주도록 하였습니다. 이 정보가 email인지, username인지, mobile number인지는 이후에 처리해주기로 하구요.

data = json.loads(request.body)

login_id = data.get('id', None)
password = data.get('password', None)

사용자 계정(id)을 입력할 수 있는 칸은 하나입니다. 받을 수 있는 값이 email, username, mobile number이라고 해서 각각의 키값으로 받으려하면 안 되고, 하나의 통일된 키(id)로 받아야 합니다.

Part2. 입력된 id가 어떤 정보인지 구분할 있는 이유

login_id 변수에 id 정보가 담겨 있습니다. 이 정보가 email인지, username인지, mobile number인지 어떻게 알 수 있을까요?

바로 회원가입 기능을 구현할 때 작성한 Validation으로 구분할 수 있습니다!
SignUpView를 다시 살펴보면 각각의 정보에 대해 정규표현식으로 Validation 조건을 주었습니다.

email_pattern         = re.compile('[^@]+@[^@]+\.[^@]+')
mobile_number_pattern = re.compile('^[0-9]{1,15}$')
username_pattern      = re.compile('^(?=.*[a-z])[a-z0-9_.]+$')

정규표현식을 간단하게 설명하면

  • email에는 @.이 꼭 들어가야 하고 얘네들 앞 뒤로 @가 아닌 값들이 들어가야 합니다.
  • mobile_number는 1자리~15자리 길이의 숫자가 입력되어야 합니다.
  • username에는 소문자, 숫자, -, .가 입력될 수 있으며, 소문자 하나 이상은 꼭 들어가야 합니다.

여기에서 @ / 숫자 / 소문자 라는 조건에 의해 각 정보는 중복되지 않습니다!

서로 겹치지 않기 떄문에 id라는 키로 정보를 같이 입력받아도 DB에서 고유한 값을 검색할 수 있을 것입니다.

part3. 회원가입된 유저인지 확인

filter()exists()를 사용해서 회원가입된 유저인지, 즉, DB에 존재하는 유저인지 확인합니다.
email, mobile_number, username 각각의 필드에서 login_id 변수에 담겨 있는 id 정보가 있는지 확인해야 합니다. Q객체와 OR 조건(|)을 사용해 간결하게 표현하였습니다.

if not User.objects.filter(
    Q(email=login_id) |
    Q(mobile_number=login_id) |
    Q(username=login_id)
).exists():
    return JsonResponse({'message': 'INVALID_USER'}, status=401)

part4. 유저 정보 가져오기

part3에서 회원가입된 유저임이 확인되었다면 유저 정보를 가져와 user라는 변수에 담습니다.
하나의 유저 정보만 존재할 것이므로 filter()가 아닌 get()을 사용해도 됩니다. get()은 객체를 반환합니다.

user = User.objects.get(
    Q(email=login_id) |
    Q(mobile_number=login_id) |
    Q(username=login_id)
)

Part5. 비밀번호 확인하기

user는 객체이므로 dot(.)으로 속성값을 가져올 수 있습니다. user.password로 사용자 계정에 매칭되는 비밀번호를 가져와서, 입력된 비밀번호인 password와 비교합니다.

if user.password != password:
    return JsonResponse({'message': 'INVALID_PASSWORD'}, status=401)

👀 로그인(LoginView) 전체 코드

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 (login_id and password):
                return JsonResponse({'message': 'KEY_ERROR'}, status=400)

            if not User.objects.filter(
                    Q(email=login_id) |
                    Q(mobile_number=login_id) |
                    Q(username=login_id)
            ).exists():
                return JsonResponse({'message': 'INVALID_USER'}, status=401)

            user = User.objects.get(
                    Q(email=login_id) |
                    Q(mobile_number=login_id) |
                    Q(username=login_id)
            )

            if user.password != password:
                return JsonResponse({'message': 'INVALID_PASSWORD'}, status=401)

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

        except JSONDecodeError:
            return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)

📬 url 경로 지정하기

# user/urls.py

from django.urls import path
from .views      import SingUpView

urlpatterns = [
    path('/signup', SingUpView.as_view()),
    path('/login', LogInView.as_view()),     --> 추가
]

다음 시간에는 bcrypt와 JWT를 사용해서 회원가입과 로그인 기능의 보안을 강화하도록 하겠습니다.

profile
Leave your comfort zone
post-custom-banner

0개의 댓글