저번 포스팅의 회원 가입 기능에 이어 로그인 기능을 구현해보도록 하겠습니다.
마찬가지로 인스타그램 홈페이지에 들어가서 로그인이 어떤 식으로 이루어지는지 파악합니다.
phone number
, username
또는 email
중 하나가 필요합니다.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'
제가 작성한 로그인 뷰를 보여드리기 전에 어떤 방식으로 작성했는지 설명드리겠습니다. 회원가입 뷰에서 설명한 내용은 생략하도록 하겠습니다.
사용자 계정에는 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)을 입력할 수 있는 칸은 하나입니다. 받을 수 있는 값이
username
,mobile number
이라고 해서 각각의 키값으로 받으려하면 안 되고, 하나의 통일된 키(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_.]+$')
정규표현식을 간단하게 설명하면
@
와 .
이 꼭 들어가야 하고 얘네들 앞 뒤로 @
가 아닌 값들이 들어가야 합니다. 숫자
가 입력되어야 합니다.소문자
, 숫자
, -
, .
가 입력될 수 있으며, 소문자
하나 이상은 꼭 들어가야 합니다. 여기에서 @
/ 숫자
/ 소문자
라는 조건에 의해 각 정보는 중복되지 않습니다!
서로 겹치지 않기 떄문에 id
라는 키로 정보를 같이 입력받아도 DB에서 고유한 값을 검색할 수 있을 것입니다.
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)
part3에서 회원가입된 유저임이 확인되었다면 유저 정보를 가져와 user
라는 변수에 담습니다.
하나의 유저 정보만 존재할 것이므로 filter()
가 아닌 get()
을 사용해도 됩니다. get()
은 객체를 반환합니다.
user = User.objects.get(
Q(email=login_id) |
Q(mobile_number=login_id) |
Q(username=login_id)
)
user
는 객체이므로 dot(.)
으로 속성값을 가져올 수 있습니다. user.password
로 사용자 계정에 매칭되는 비밀번호를 가져와서, 입력된 비밀번호인 password
와 비교합니다.
if user.password != password:
return JsonResponse({'message': 'INVALID_PASSWORD'}, status=401)
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)
# 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를 사용해서 회원가입과 로그인 기능의 보안을 강화하도록 하겠습니다.