[Django, JWT] Django에서의 JWT 적용

코딩은 돈이 된다·2024년 6월 28일

[JWT, Redis] JWT, Redis를 활용한 로그인, 로그아웃

해당 글을 작성하면서 django를 언급했는데 이번 글은 django에서의 jwt 사용 예시이다.
해당 포스트에서 언급했던 것처럼 django에는 simplejwt 라는 라이브러리로 jwt 적용을 지원해준다. 이번 글에서는 해당 라이브러리를 사용하여 jwt를 적용하는 방법을 알아볼 것이다.

설치

pip install djangorestframework-simplejwt

settings.py

INSTALLED_APPS = [
	...
    'rest_framework_simplejwt.token_blacklist',
    ...
]
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}
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': True,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',
}

메인 프로젝트의 settings.py에 해당 항목을 추가해야 한다.
token_blacklist는 jwt의 블랙리스트를 지원하기 위한 항목이다.
밑에 SIMPLE_JWT는 jwt 토큰의 기본 설정이다.

urls.py

   path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
   path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),

해당 url을 urlpatterns에 추가해야한다.

로그인, 로그아웃

class LoginView(APIView):
    @swagger_auto_schema(operation_id="사용자 로그인")
    def post(self, request):
        try:
            data = JSONParser().parse(request)
            serializer = LoginSerializer(data=data)
            if serializer.is_valid(raise_exception=True):
                user = serializer.validated_data['user']
                refresh = RefreshToken.for_user(user)

                return Response({
                    'refresh': str(refresh),
                    'access': str(refresh.access_token),
                }, status=200)
        except Exception as e:
            return Response("로그인에 실패했습니다.", status=400)

로그인 코드를 확인해보면 토큰을 발급 후에 유저에게 반환해준다.

class LogoutView(APIView):
    @swagger_auto_schema(operation_id="사용자 로그아웃")
    def post(self, request):
        try:
            refresh_token = request.data['refresh']
            if refresh_token is None:
                return Response("리프레시 토큰이 필요합니다.", status=400)

            token = RefreshToken(refresh_token)
            token.blacklist()

            return Response("로그아웃되었습니다.", status=205)
        except Exception as e:
            return Response({"detail": "로그아웃에 실패했습니다.", "error": str(e)}, status=400)

로그아웃 코드를 확인해보면, 리프레시 토큰을 받아서 블랙리스트에 등록하고 응답을 해준다.

인증

class QuestionAllView(APIView):
    @swagger_auto_schema(
        operation_id="질문 리스트 불러오기",
    )
    def get(self, request):
        question = Question.objects.all()
        serializer = QuestionAllSerializer(question, many=True)
        return Response(serializer.data)

    @swagger_auto_schema(
        request_body=QuestionAddSerializer,
        operation_id="질문 추가",
    )
    def post(self, request):
        serializer = QuestionAddSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

    def get_permissions(self):
        if self.request.method == 'POST':
            self.authentication_classes = [JWTAuthentication]
            self.permission_classes = [IsAuthenticated]
        return super().get_permissions()

인증 부분을 다룰 것이다. 여기선 POST요청에 대해서만 인증이 필요하도록 설정해 두었다.
이렇게 하면 인증이 필요한 서비스에서만 접근할 수 있다.

올바르지 않은 엑세스 토큰으로 접근을 하는 경우(또는 토큰이 없는 경우) 권한이 없음을 알려준다.


많은 코드를 작성하지 않고 몇 가지의 코드를 추가한 것 만으로도 jwt에 대한 기능을 편하게 사용할 수 있다는 것이 큰 장점인 것 같다.

추가

프로젝트를 진행하면서, 일반 user테이블에 simplejwt를 적용하려고 했는데 오류가 나길래
이것저것 찾아보니, Django에는 기본으로 제공되는 User가 존재하는데, 해당 User에서만 동작하는 것이었다.

해결방법은 AbstractBaseUser를 가져와주면 되는데, 기본 제공 필드들이 많지만, USERNAME_FIELD라는 식별 가능한 필드를 선언해야하고, REQUIRED_FIELDS라는 식별 가능한 필드 이외의 필요한 필드들을 선언해주어야 한다.

class user(AbstractBaseUser):
    user_email = models.EmailField(max_length=100, unique=True)
    user_name = models.CharField(max_length=100)
    user_password = models.CharField(max_length=200)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    deleted_at = models.DateTimeField(null=True, blank=True)

    USERNAME_FIELD = "user_email"
    REQUIRED_FIELDS = ["user_name", ]

    def __str__(self):
        return self.user_name

나는 이렇게 해서 email을 unique필드로 정의하고(unique 필드로 정의 안하고 USERNAME_FIELD에 선언하면 오류가 난다..)

작성한 뒤에 문제 없이 simplejwt를 사용했다.

0개의 댓글