DevOps/Django

[DRF] simple-jwt와 cumstom user

bestwish 2022. 3. 27. 00:13

공식문서를 보면서 jwt와 user를 사용해보려고 한다.
여기서 소셜인증은 사용하지 않는다.

 

 

환경세팅

1. 패키지 설치

pip install djangorestframework
pip install djangorestframework-simplejwt
pip install django-allauth
pip install dj-rest-auth

2. settings.py

# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',
    'rest_framework.authtoken',
    'rest_framework_simplejwt.token_blacklist',
    'allauth',
    'allauth.account'
    'dj_rest_auth',
    'dj_rest_auth.registration',

    # app
    'bwh',
    'accounts',
]

SITE_ID = 1
AUTH_USER_MODEL = 'accounts.User'

...


REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
    )
}

REST_USE_JWT = True

여기까지 설정을 dj-rest-auth문서 dj-rest-auth 공식문서 링크를 보고 설정할 수 있다.
AUTH_USER_MODEL은 accounts앱의 models.py에서 User를 사용하겠다고 설정한 부분이다.
다음으로 django-allauth 설정을 이어서 한다.

# settings.py
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'

django-allauth를 보고 작성했다.
작성한 명령어들의 설명은 다음과 같다.

  • ACCOUNT_USER_MODEL_USERNAME_FIELD : 커스텀 사용자 모델을 사용하는 경우 아이디 필드의 이름이 username이 아닌 다른 이름일 경우 지정한다.
    만약 None으로 지정할 경우 allauth에서 username과 관련된 모든 기능을 사용하지 않는다.
    이 경우 ACCOUNT_USERNAME_REQUIRED 값 또한 반드시 False로 지정해야 한다.
  • ACCOUNT_EMAIL_REQUIRED : 회원가입할 때 이메일 주소 입력 필수 여부이다. 디폴트 값은 False이다.
  • ACCOUNT_USERNAME_REQUIRED : 회원 가입할 때 username 입력 필수 여부이다. 디폴트 값은 True이므로 반드시 ACCOUNT_AUTHENTICATION_METHOD를 통해 이메일로 로그인으로 설정하더라도 username을 입력해야 가입된다.
  • ACCOUNT_AUTHENTICATION_METHOD : 로그인 인증 방법으로 username, email, username_email을 지정할 수 있다. email로 설정할 때는 ACCOUNT_EMAIL_REQUIRED = True 옵션을 같이 설정해야 한다.
# settings.py
from datetime import timedelta

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'ROTATE_REFRESH_TOKENS': False,
    'BLACKLIST_AFTER_ROTATION': False,
    'UPDATE_LAST_LOGIN': False,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,
    'JWK_URL': None,
    'LEEWAY': 0,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',
    'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}

simple-jwt를 보고 작성했다.

5번 라인과, 6번 라인을 통해 토큰의 유효기간을 정할 수 있다.

3. urls.py

# urls.py
from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('bwh/', include('bwh.urls')),
    path('accounts/', include('accounts.urls')),
]

 

 


 

 

app Setting

1. app/models.py

# accounts/models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class User(AbstractBaseUser):
    email = models.EmailField('EMAIL', max_length=100, null=False, blank=False, unique=True)
    nickname = models.CharField('NICKNAME', max_length=100, null=False, blank=False)
    profile_image = models.ImageField('PROFILE_IMG', upload_to='user/%Y/%m', blank=True, null=True)

    USERNAME_FIELD = 'email'

    def __str__(self):
        return self.nickname

    class Meta:
        db_table = 'accounts'

회원가입에서는 AbstractBaseUser를 사용한다.
AbstractBaseUser는 간단하게 설명하면 username 컬럼을 사용 안할 때 상속하여 사용한다.
AbstractUser는 username 컬럼을 사용할 때 상속한다.

 

2. app/serializers.py

# accounts/serializers.py
from rest_framework import serializers
from dj_rest_auth.registration.serializers import RegisterSerializer
from rest_framework.validators import UniqueValidator
from .models import User

class CustomRegisterSerializer(RegisterSerializer):
    '''
    회원가입 Custom
    '''
    username = None
    nickname = serializers.CharField(required=True, validators=[UniqueValidator(queryset=User.objects.all(), message=("Name already exists"))])
    profile_image = serializers.ImageField(use_url=True)

    def get_cleaned_data(self):
        data = super().get_cleaned_data()
        data['profile_image'] = self.validated_data.get('profile_image', '')
        data['username'] = self.validated_data.get('username')

        return data

serializers에서 사용하는 field에 대한 설명은 drf 문서에 설명이 잘 나와 있다.
RegisterSerializer는 기본적으로 username, email, password만 받아준다. models.py에 추가한 nickname과 profile_image도 넣어주기 위해 get_cleaned_data를 오버라이딩 해준다.
그리고 custom을 했다면 settings.py에 serializers를 재정의 해줘야한다.

# settings.py
REST_AUTH_REGISTER_SERIALIZERS = {
    'REGISTER_SERIALIZER': 'accounts.serializers.CustomRegisterSerializer',
}

 

3. app/adapter.py

RegisterSerializer를 상속받아 커스텀하였을 때, 추가한 필드들은 입력값이 저장이 안된다.
이 문제를 해결하기 위해서는 adapter도 수정해야 한다.
참고 : 참고 블로그

# accounts/adapter.py
from allauth.account.adapter import DefaultAccountAdapter
from allauth.account.utils import user_field


class CustomAccountAdapter(DefaultAccountAdapter):
    def save_user(self, request, user, form, commit=True):
        data = form.cleaned_data

        user = super().save_user(request, user, form, False)

        # 추가 필드
        nickname = data.get('nickname')
        profile_image = data.get('profile_image')

        if nickname:
            user.nickname = nickname
        if profile_image:
            user.profile_image = profile_image

        user.save()

        return user

allauth.account.adapter.DefaultAccountAdapter에서 save_user를 가져와서 오버라이딩을 해줬다.

 

4. app/urls.py

# accounts/urls.py
from django.urls import path, include

urlpatterns = [
    path('', include('dj_rest_auth.urls')),
    path('signup/', include('dj_rest_auth.registration.urls')),
]

 

 


 

 

결과

이제 로그인을 하면 토큰과 user의 pk, email이 나타난다.