해당 포스트는 DRF 공식문서 Tutorial을 해봤다는 가정하에 작성하였습니다.
해당 포스트에서는 jwt를 사용하여 회원 체크를 진행할 것입니다.
jwt를 사용하기 위해 package를 설치하겠습니다.
pip install djangorestframework-jwt
이 프로젝트에서 drf와 jwt를 사용하기 위해서 api_server/settings.py에 세팅을 추가적으로 진행하겠습니다. (위치는 큰 상관없지만 새로 추가하는 것은 가장 아래 쪽에 작성하겠습니다.)
drf 설정
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
],
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser'
]
}
jwt 설정 작성
from datetime import timedelta #(이 부분은 상단에 위치)
JWT_AUTH = {
'JWT_ENCODE_HANDLER':
'rest_framework_jwt.utils.jwt_encode_handler',
'JWT_DECODE_HANDLER':
'rest_framework_jwt.utils.jwt_decode_handler',
'JWT_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_payload_handler',
'JWT_PAYLOAD_GET_USER_ID_HANDLER':
'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler',
'JWT_RESPONSE_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_response_payload_handler',
'JWT_SECRET_KEY': 'SECRET_KEY',
'JWT_GET_USER_SECRET_KEY': None,
'JWT_PUBLIC_KEY': None,
'JWT_PRIVATE_KEY': None,
'JWT_ALGORITHM': 'HS256',
'JWT_VERIFY': True,
'JWT_VERIFY_EXPIRATION': True,
'JWT_LEEWAY': 0,
'JWT_EXPIRATION_DELTA': timedelta(days=30),
'JWT_AUDIENCE': None,
'JWT_ISSUER': None,
'JWT_ALLOW_REFRESH': False,
'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=30),
'JWT_AUTH_HEADER_PREFIX': 'Bearer',
'JWT_AUTH_COOKIE': None,
}
jwt 설정에 관한 내용은 djangorestframework-jwt 공식문서 를 참고해주시길 바랍니다.
아래 내용을 accounts/models.py 작성
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self.create_user(email, password, **extra_fields)
class User(AbstractBaseUser, PermissionsMixin):
"""
customized User
"""
email = models.EmailField(
verbose_name=_('email id'),
max_length=64,
unique=True,
help_text='EMAIL ID.'
)
username = models.CharField(
max_length=30,
)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def __str__(self):
return self.username
def get_short_name(self):
return self.email
model에 대한 자세한 정보는 구글에 커스텀 유저 모델로 검색 후 참고해주시면 감사하겠습니다.
Settings.py 세팅 추가
AUTH_USER_MODEL = 'accounts.User'
우선 accounts 앱에 serializers.py 라는 파일을 생성합니다.
touch accounts/serializers.py
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
from django.contrib.auth import get_user_model
from .models import User
User = get_user_model()
class UserCreateSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
username = serializers.CharField(required=True)
password = serializers.CharField(required=True)
def create(self, validated_data):
user = User.objects.create(
email=validated_data['email'],
username=validated_data['username'],
)
user.set_password(validated_data['password'])
user.save()
return user
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from .serializers import UserCreateSerializer
from .models import User
@api_view(['POST'])
@permission_classes([AllowAny])
def createUser(request):
if request.method == 'POST':
serializer = UserCreateSerializer(data=request.data)
if not serializer.is_valid(raise_exception=True):
return Response({"message": "Request Body Error."}, status=status.HTTP_409_CONFLICT)
if User.objects.filter(email=serializer.validated_data['email']).first() is None:
serializer.save()
return Response({"message": "ok"}, status=status.HTTP_201_CREATED)
return Response({"message": "duplicate email"}, status=status.HTTP_409_CONFLICT)
먼저 urls.py 파일을 생성합니다.
touch accounts/urls.py
아래 내용을 urls.py에 추가합니다.
from django.urls import path
from . import views
urlpatterns = [
path('create/', views.createUser),
]
또한 메인 프로젝트인 api_server의 urls에 아래 내용을 추가합니다.
from django.contrib import admin
from django.urls import path
# 추가
from django.conf.urls import include
urlpatterns = [
path('admin/', admin.site.urls),
# 추가
path('users/', include('accounts.urls')),
]
우선 여기서 테스트는 postman을 활용해서 진행하겠습니다. 포스트맨 설치
accounts 모델의 변경사항이 있기 때문에 migrate를 진행합니다
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency users.0001_initial on database 'default'.
- 위의 작업을 진행하다가 위와 같은 에러가 발생한 경우 db.sqlite 파일과 각 앱에 있는 migrations 폴더 안에 있는 숫자로 시작하는 파일을 지운 이후 다시 위의 명령어를 실행하면 됩니다.
Postman에서 api 테스트
아래의 이미지처럼 테스트를 진행하면 됩니다.
accounts의 serializers에 로그인 api에 필요한 serializer를 작성합니다.
from django.contrib.auth.models import update_last_login
from django.contrib.auth import authenticate
JWT_PAYLOAD_HANDLER = api_settings.JWT_PAYLOAD_HANDLER
JWT_ENCODE_HANDLER = api_settings.JWT_ENCODE_HANDLER
class UserLoginSerializer(serializers.Serializer):
email = serializers.CharField(max_length=64)
password = serializers.CharField(max_length=128, write_only=True)
token = serializers.CharField(max_length=255, read_only=True)
def validate(self, data):
email = data.get("email", None)
password = data.get("password", None)
user = authenticate(email=email, password=password)
if user is None:
return {
'email': 'None'
}
try:
payload = JWT_PAYLOAD_HANDLER(user)
jwt_token = JWT_ENCODE_HANDLER(payload)
update_last_login(None, user)
except User.DoesNotExist:
raise serializers.ValidationError(
'User with given email and password does not exists'
)
return {
'email': user.email,
'token': jwt_token
}
from .serializers import UserLoginSerializer
@api_view(['POST'])
@permission_classes([AllowAny])
def login(request):
if request.method == 'POST':
serializer = UserLoginSerializer(data=request.data)
if not serializer.is_valid(raise_exception=True):
return Response({"message": "Request Body Error."}, status=status.HTTP_409_CONFLICT)
if serializer.validated_data['email'] == "None":
return Response({'message': 'fail'}, status=status.HTTP_200_OK)
response = {
'success': 'True',
'token': serializer.data['token']
}
return Response(response, status=status.HTTP_200_OK)
accounts/urls.py의 urlpatterns에 추가
path('login/', views.login),
회원가입 테스트와 동일하게 postman에서 테스트를 진행합니다.
로그인이 제대로 진행되면 token 값을 반환합니다.
해당 프로젝트는 jwt를 활용하여 회원 인증을 진행하기 때문에 로그아웃은 서버에서 진행하지 않습니다.
포스트의 코드는 github에 업로드되어있습니다.
다음 포스트에서는 간단한 게시글 작성을 viewset을 활용해서 만들어 보겠습니다.
너무 궁금해서 댓글남겨요 ..
def create(self, validated_data):
user = User.objects.create(
email=validated_data['email'],
username=validated_data['username'],
)
user.set_password(validated_data['password'])
user.save()
return user
이부분에보면 User.objects.create_user 를 안쓰고 User.objects.create 를 쓰는 이유가있나요.. ?
그리고 어떻게 User.objects.create 가 작동하는거죠..?
objects 는 Usermanager 고 Usermanager 안에 define 된 함수는 create_user 밖에없는데 어덯게
objects.create 가 작동할수있는지.. 너무 궁금하네요ㅣ.
글 잘읽었습니다! 실습 따라 해봤는데 로그인 api 생성 파트에서 serializers.py에
api_settings가 정의가 안되어있다는 문제가 발생해서
from rest_framework_jwt.settings import api_settings
를 추가해서 해결했습니다!