Post

[Django] dj-rest-auth를 활용한 JWT 회원가입/로그인 구현

Django dj-rest-auth를 활용한 JWT 회원가입/로그인 구현

😂 서론

직접 views.pyserializers.py를 작성하면서 simplejwt를 사용해서 회원가입/로그인/로그아웃 등을 구현해봤었다. 검색하면서 구현해보다가 관련 기능을 제공하는 것들이 있는 것 같은데, 그 때는 애초에 로그인 API 구현을 처음해보고 jwt token을 처음 접해서 그런지 이해가 안갔다. 계속해서 해보다보디 조금씩 이해가 되면서 dj-rest-auth라는 아주 간편한 도구가 있어서 사용했다.

✅ 구현해봅시다

Django에서는 로그인 할 때 기본적으로 username을 사용하는데 나는 email을 사용할 것이고 추가적인 필드가 필요했다. 그래서 BaseUserManagerAbstractUser를 사용해서 user 테이블을 커스텀 했다.

먼저 회원관련 API를 제공할 accounts라는 앱을 생성하고 settings.py에 추가해준다.

settings.py에 아래 코드를 추가해야한다. 추가하지 않으면 인식 못함 AUTH_USER_MODEL = '{앱이름}.{클래스명}' => AUTH_USER_MODEL = 'accounts.User'

✔️ accounts/managers.py

accounts 폴더 안에 managers.py를 생성하고 아래와 같이 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import gettext_lazy as _

class UserManager(BaseUserManager):
    def create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError(_('Users must have an email address'))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        superuser = self.create_user(
            email=email,
            password=password,
        )
        superuser.is_staff = True
        superuser.is_superuser = True
        superuser.is_active = True
        superuser.is_admin = True
        superuser.save(using=self._db)
        return superuser

✔️ accounts/models.py

accounts/models.py로 와서 아래와 같이 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import gettext_lazy as _

from .managers import UserManager

class User(AbstractUser):
    username = None
    email = models.EmailField(_('email address'), unique=True, null=False, blank=False)
    # 추가적으로 넣을 field들 작성
    hospitalID = models.ForeignKey("Hospital", on_delete=models.CASCADE, blank=True, null=True, db_column="hospitalID")
    
    USERNAME_FIELD = 'email' # username field를 email로 사용
    REQUIRED_FIELDS = []

    # 헬퍼 클래스
    objects = UserManager()
    
    @property
    def name(self):
        return f"{self.first_name} {self.last_name}"
    
    def __str__(self):
        return self.email
    class Meta:
        db_table = 'user' # 테이블 이름 설정

✔️ accounts/urls.py

메인 urls.py에서 연결시키는건 잊지 않았쥬?

1
2
3
4
5
urlpatterns = [
    ...
    path('accounts/',include('accounts.urls')),
    ...
]

accounts/urls.py

1
2
3
4
5
6
7
8
9
from django.urls import path, include

urlpatterns = [
    ...
    # 일반 회원 회원가입/로그인
    path('', include('dj_rest_auth.urls')),
    path('registration/', include('dj_rest_auth.registration.urls')),
    ...
]

✔️ 라이브러리

1
2
# 라이브러리 설치
pip install djangorestframework djangorestframework-simplejwt dj-rest-auth django-allauth 
  • djangorestframework : Django를 REST API 형태로 사용할 수 있도록 함
  • djangorestframework-simplejwt : Django에서 JWT Token을 사용하도록 함
    • 근데 dj-rest-auth에서 제공을 하는건지 안써도 되는 것 같기도하고..
  • dj-rest-auth : 로그인, 로그아웃 등의 기능을 REST API 형태로 제공
    • 얘가 계속 업데이트 되는지, 기존 다른 분들의 블로그 글에서 작성한 파라미터 값으로는 에러가 났었음. 공식 문서를 참고해야 함
  • allauth : 회원가입 기능을 제공

settings.py에 새로 설치한 앱 추가 및 변수 세팅

  • 여기서 부터는 공식문서를 참고하는게 좋다.
  • simplejwt는 안써도 되는 것 같기도 한데 잘 모르겠다..
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
INSTALLED_APPS = [
    ...
    # 설치한 라이브러리
    'rest_framework',
    # 'rest_framework_simplejwt',
    'rest_framework.authtoken',
    'dj_rest_auth',
    'dj_rest_auth.registration',
    'allauth',
    'allauth.account',
    
    # Locals Apps: 생성한 app
    "accounts",
]

# default AUTH_USER 
AUTH_USER_MODEL = 'accounts.User'

# dj-rest-auth
REST_AUTH = {
    'USER_DETAILS_SERIALIZER': 'dj_rest_auth.serializers.UserDetailsSerializer',
    'USE_JWT': True,
    'JWT_AUTH_COOKIE': 'my-app-auth',
    'JWT_AUTH_REFRESH_COOKIE': 'my-refresh-token',
    'JWT_AUTH_HTTPONLY' : False,
}

# allauth
SITE_ID = 1
ACCOUNT_USER_MODEL_USERNAME_FIELD = None # username 필드 사용 x
ACCOUNT_EMAIL_REQUIRED = True            # email 필드 사용 o
ACCOUNT_USERNAME_REQUIRED = False        # username 필드 사용 x
ACCOUNT_AUTHENTICATION_METHOD = 'email'

ACCOUNT_EMAIL_VERIFICATION = 'none' # 회원가입 과정에서 이메일 인증 사용 X
OLD_PASSWORD_FIELD_ENABLED = True # 비밀번호 변경 시 예전 비밀번호 입력


# REST_FRAMEWORK settings:
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
        'rest_framework.permissions.IsAdminUser',
    ],
     'DEFAULT_AUTHENTICATION_CLASSES': [
        'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
    ]
}

# SIMPLE_JWT = {
#     'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30), 
#     'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
#     'ROTATE_REFRESH_TOKENS': False, # True일 경우: TokenRefeshView 에 retresh token을 보내면 새로운 access/refresh token 발급
#     'BLACKLIST_AFTER_ROTATION': False, # True 일 시: 새로 고침 토큰 이 블랙리스트에 추가
#     'UPDATE_LAST_LOGIN': False, # True 일 시: User 테이블의 last_login 필드가 로그인 시 업데이트

#     'ALGORITHM': 'HS256',
#     'SIGNING_KEY': SECRET_KEY,
#     'VERIFYING_KEY': None,
#     'AUDIENCE': None,
#     'ISSUER': None, # host site의 도메인
#     'JWK_URL': None,
#     'LEEWAY': 0,

#     'AUTH_HEADER_TYPES': ('Bearer',),
#     'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', # HTTP_  뒤 부분이 token 헤더 이름이 됨
#     '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),
# }

✨ API 테스트

POSTMAN으로 테스트 해보면 잘 되는걸 확인할 수 있다.

✔️ 회원가입

회원가입

✔️ 로그인

로그인

✔️ 회원정보 가져오기

회원정보 가져오기

✔️ 비밀번호 변경

비밀번호 변경

✔️ 로그아웃

로그아웃

✔️ AccessToken 재발급

AccessToken 재발급

This post is licensed under CC BY 4.0 by the author.

[Django] Django 프로젝트 생성 기초

[Django] SerializerMethodField로 원하는 JSON 보내주기