[DRF] Simple-jwt로 회원가입, 로그인 기능

Cherry·2022년 6월 7일
10

DRF-JWT

drf-jwt
원래 drf로 회원가입을 만들때 jwt가 가장 많이 사용된다고 한다. 그러나 Release History 를 보면 2017년이 마지막인걸 볼 수 있고, 개발이 다 끝나고 나서 약간의 문제점은 처음 username, password 를 통해 인증 받을 때, 토큰 (access_token) 하나만 보내준다. 그리고 나서 access_token 갱신 할 때, refresh_token 을 통해서 access_token 갱신을 해 줘야되는데 여기서 refresh_token 을 가지고 있지 않아서 access_token 갱신이 어려웠다. 그리고 결정적으로 jwt를 만든사람도 simple jwt사용하라고 하길래 바로 simple-jwt에 대해 알아보기 시작했다.

Simple-JWT

Simpe-JWT는 DRF을 위해 JSON Web Token authentication을 제공한다. 라이브러리 사용 방법은 djangorestframework-jwt 와 비슷하고 처음 인증할때, access_token, refresh_token 둘 다 보내준다. 그래서 djangorestframework-simplejwt로 개발하게 되었다.

설치 및 세팅

우선 simple-jwt를 설치해주고 세팅해주어야 한다.

pip install djangorestframework-simplejwt
# settings.py

# 유저도 settings에서 꼭 설정해주어야 함
AUTH_USER_MODEL = 'product.User'

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'product',
    'rest_framework',
    # simple-jwt 추가해주기 
    'rest_framework_simplejwt',
]

...

REST_FRAMEWORK = {
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        ...
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
    ...
}

...

# 추가적인 JWT_AUTH 설정
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
    '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 = {} 라고 되어 있는 부분은 다 쓸 필요는 없고 필요한 부분만 커스텀해서 작성해주면 된다.

User Model 커스텀하기

Django에 내장된 유저 모델은 프로젝트와 맞지 않은 부분이 많아서 커스텀해주기로 했다.

class UserManager(BaseUserManager):
    use_in_migrations = True

    def create_user(self, login_id, email, password, **kwargs):
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            login_id=login_id,
            email=email,
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

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


class User(AbstractBaseUser, PermissionsMixin):
    login_id = models.CharField(max_length=30, unique=True, null=False, blank=False)
    email = models.EmailField(max_length=30, unique=True, null=False, blank=False)
    is_superuser = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = UserManager()

    USERNAME_FIELD = 'login_id'
    REQUIRED_FIELDS = ['email']

    class Meta:
        db_table = 'user'

회원가입하기

회원가입할때부터 JWT token을 발급해주기로 했다.

# serialzier.py

User = get_user_model()

class RegisterSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'

    def create(self, validated_data):
        login_id = validated_data.get('login_id')
        email = validated_data.get('email')
        password = validated_data.get('password')
        user = User(
            login_id=login_id,
            email=email
        )
        user.set_password(password)
        user.save()
        return user
        

# views.py

# 회원가입
class RegisterAPIView(APIView):
    def post(self, request):
        serializer = RegisterSerializer(data=request.data)
        if serializer.is_valid():
            user = serializer.save()
            # jwt token 접근해주기
            token = TokenObtainPairSerializer.get_token(user)
            refresh_token = str(token)
            access_token = str(token.access_token)
            res = Response(
                {
                    "user": serializer.data,
                    "message": "register successs",
                    "token": {
                        "access": access_token,
                        "refresh": refresh_token,
                    },
                },
                status=status.HTTP_200_OK,
            )
            #쿠키에 넣어주기...아직 어떤식으로 해야될지 모르겠는데 이렇게 설정만 우선 해주었다. 
            res.set_cookie("access", access_token, httponly=True)
            res.set_cookie("refresh", refresh_token, httponly=True)
            return res
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
# urls.py

urlpatterns = [
    path("", include(router.urls)),
    path("register/", RegisterAPIView.as_view()), #회원가입하기
]

포스트맨으로 테스트 해주면 아래처럼 생성된 유저하고 토큰들이 발급된다.

코드가 복잡해져서 이렇게 사용하는게 맞는지 모르겠는데 추후에 조금 더 수정할것이다.

로그인하기

# serialzier.py

User = get_user_model()

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'
        

# views.py

# 로그인
class AuthView(APIView):

    def post(self, request):
        user = authenticate(
            login_id=request.data.get("login_id"), password=request.data.get("password")
        )
        if user is not None:
            serializer = UserSerializer(user)
            token = TokenObtainPairSerializer.get_token(user)
            refresh_token = str(token)
            access_token = str(token.access_token)
            res = Response(
                {
                    "user": serializer.data,
                    "message": "login success",
                    "token": {
                        "access": access_token,
                        "refresh": refresh_token,
                    },
                },
                status=status.HTTP_200_OK,
            )
            return res
        else:
            return Response(status=status.HTTP_400_BAD_REQUEST)
        
# urls.py

urlpatterns = [
    path("", include(router.urls)),
    path("register/", RegisterAPIView.as_view()), #회원가입하기
    path("auth/", AuthView.as_view()), #로그인하기
]

포스트맨으로 로그인 테스트를 해보았다.

아직 로그아웃을 구현하지 못했는데 다음에는 로그아웃도 구현하는것도 추후에 할 예정이다

토큰 재발급하기

access_token은 5분만 지나면 만료되기 때문에 refresh_token을 가지고 access_token을 재발급해야 된다. 이는 따로 코드를 구현하지 않고 simplejwt에 내장된 기능으로 구현했다.

# urls.py

from rest_framework_simplejwt.views import TokenRefreshView

urlpatterns = [
    path("", include(router.urls)),
    path("register/", RegisterAPIView.as_view()), #회원가입하기
    path("auth/", AuthView.as_view()), #로그인하기
    path('auth/refresh/', TokenRefreshView.as_view()),#토큰 재발급하기
]

이렇게 설정을 해주고 포스트맨에 refresh_token과 함께 전송을 하면 아래와 같이 access_token이 다시 발급된다.

1개의 댓글

comment-user-thumbnail
2022년 10월 31일

감사합니다.

답글 달기