[DRF] 회원가입, 로그인 기능(JWT)

애이용·2021년 1월 30일

django

목록 보기
3/4
post-thumbnail

이번 포스팅은 DRF로 회원가입/로그인 기능 구현이다.
요 기능들은 한 번 정리하면 나중에 도움될 듯하여 정리한다

JWT(Json Web Token)

🔨 프로젝트 세팅

패키지 설치

$ pip install djangorestframework-jwt
$ pip install django-rest-authtoken

settings.py

# settings.py

INSTALLED_APPS = [
	...,
	'rest_framework.authtoken',
    	...
]

AUTH_USER_MODEL = 'my_user.User'

## DRF 
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated', # 인증된 사용자만 접근 가능
        'rest_framework.permissions.IsAdminUser', # 관리자만 접근 가능
        'rest_framework.permissions.AllowAny', # 누구나 접근 가능

    ),

    'DEFAULT_RENDERER_CLASSES': (
        # 자동으로 json으로 바꿔줌
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ),


    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        # 'rest_framework.authentication.TokenAuthentication',
        # 'rest_framework.authentication.SessionAuthentication',
        # 'rest_framework.authentication.BasicAuthentication',
    ),
}

## JWT
# 추가적인 JWT_AUTH 설젇
JWT_AUTH = {
    'JWT_SECRET_KEY': SECRET_KEY,
    'JWT_ALGORITHM': 'HS256', # 암호화 알고리즘
    'JWT_ALLOW_REFRESH': True, # refresh 사용 여부
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 유효기간 설정
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=28), # JWT 토큰 갱신 유효기간
    # import datetime 상단에 import 하기
}

✨ Custom User Model

이 프로젝트에서 my_user 앱을 생성해 my_user/models.py에 User 모델을 작성하겠다.

  • AbstractBaseUser 상속 👉 password 컬럼 필요 없음
  • settings.py 👉 AUTH_USER_MODEL = 'my_user.User' 추가
    : 추가하지 않을 시, makemigrations 에러
# my_user/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

🎈 회원가입 기능

✔ serializers.py

User = get_user_model()

class UserCreateSerializer(serializers.Serializer):
    email = serializers.EmailField(required=True)
    username = serializers.CharField(required=True)
    password = serializers.CharField(required=True)
    print(email)
    def create(self, validated_data):
        user = User.objects.create( # User 생성
            email=validated_data['email'],
            username=validated_data['username'],
        )
        user.set_password(validated_data['password'])

        user.save()
        return user

✔ views.py

@api_view(['POST']) 
@permission_classes([AllowAny]) # 인증 필요없다
def signup(request):
    serializer = UserCreateSerializer(data=request.data) 
    if serializer.is_valid(raise_exception=True):
        serializer.save() # DB 저장
        return Response(serializer.data, status=201) 

처음에 클래스 내에 함수 정의했다가... 계속 UnAuthorized(401) 코드 나와서 그냥 밖으로 빼내서
@permission_classes([AllowAny]) 설정했더니 되었다

회원가입 기능은 완료되었다.
User 모델 작성하는 것만 제대로 할 수 있다면 나머지는 수월 !~!
다만 나는 @permission_classes([AllowAny]) 요 속성을 이번에 알아서 매우 헤맸..

🎈 로그인 기능

로그인을 할 때는, JWT를 이용해 토큰 발행까지 진행 !

✔ serializers.py

User = get_user_model()

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
        }

✔ views.py

@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": # email required
            return Response({'message': 'fail'}, status=status.HTTP_200_OK)

        response = {
            'success': True,
            'token': serializer.data['token'] # 시리얼라이저에서 받은 토큰 전달
        }
        return Response(response, status=status.HTTP_200_OK)

✔ urls.py 에 꼭 추가하기 ❗

my_user/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('signup', views.signup),
    path('login', views.login),
]

✨ Header에 token 전달하기

  • KEY : Authorization
  • VALUE : JWT [token]

DRF에 회원가입, 로그인 기능을 적용하는 건... 한 3시간? 정도 걸린 듯하다
계속해서 401 코드가 응답되고, csrf 에러, migration 에러 등등..
처음에 확장 모델이 아닌 그냥 처음에 User 모델 자체를 첨부터 만들어서 authentication 적용하는 법도 엄청 어려웠다.
그래서 다 지우고, 새로 my_user app 생성해서 만들고,, 엄청 많은 수정을 거쳐 성공했다
(여기서 db.sqlite3, migrations 폴더 지우고 다시 makemigrations 명령어를 치면 된다는 것도 알게됨 구글링 최고 ㅎㅎ)

어서 빨리 Post, Comment 다시 User 외래키로 넣어서 테스트해봐야지
DRF 꽤 재밌네 😎

profile
로그를 남기자 〰️

1개의 댓글

comment-user-thumbnail
2022년 10월 31일

감사합니다.

답글 달기