TokenObtainPairSerializer 사용 중에 발생한 'Expected a string value' 에러 Troubleshooting

김동욱·2023년 12월 21일
0

Troubleshooting

목록 보기
1/14
post-thumbnail

문제 상황

DRF를 활용하여 JWT 인증을 구현하다 마주한 문제 상황이다. 우선 로그인 기능을 다음과 같이 구현했다.

class UserLoginAPIView(APIView):
    permission_classes = [AllowAny]

    def post(self, request: Request):
        username = request.data['username']
        password = request.data['password']

        user = User.objects.filter(username=username).first()

        if user is None:
            return Response(
                {"message": "등록되지 않은 사용자입니다."},
                status=status.HTTP_400_BAD_REQUEST
            )

        if not check_password(password, user.password):
            return Response(
                {"message": "비밀번호가 틀렸습니다."},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            token = TokenObtainPairSerializer.get_token(user)
            return Response(
                {
                    "message": "로그인에 성공했습니다.",
                    "data": {
                        "access": str(token.access_token),
                        "refresh": str(token)
                    },
                },
                status=status.HTTP_200_OK
            )
        except Exception as e:
            logger.error(e)
            return Response({"message": "로그인 처리 중 문제가 발생했습니다."},
                            status=status.HTTP_400_BAD_REQUEST)

post method로 api 호출할 때 에러 메세지로 Expected a string value 라는 문구가 콘솔창에 찍혔다. TokenObtainPairSerializer의 get_token() 메서드 호출 시 발생하는 것 같아서 관련된 내용을 찾아 보다가 나와 비슷한 문제에 직면한 글을 stackoverflow에서 발견했다. 한 가지 다른 점은 나는 simple-jwt를 사용하여 구현한 것과 달리 작성자는 PyJWT를 사용한 것이다. 하지만 문제 해결에 도움이 됐다.

해결 방법

답변 중 JWT_SECRET_KEY 값을 설정해야 한다는 내용이 있었다.(물론 나는 simple-jwt를 사용했기 때문에 SIGNING_KEY를 설정해야 했다.) 공식 문서에 따르면 SIGNING_KEY 값을 따로 설정하지 않으면 SECRET_KEY 값이 기본값으로 설정된다고 한다.

분명 SECRET_KEY 값을 설정했고, 값이 정상적으로 불러와지는 것을 확인했으나 에러 해결이 되지 않았다. 그러던 중 문제가 발생하는 원인을 찾았다. 설정 파일을 분리해서 개발하면서 놓쳤던 부분이 있었던 것이었다.

우선 나는 개발 환경을 local과 prod 환경으로 나누어서 개발을 진행 중이다. local.py와 prod.py는 base.py 내용을 import 한 후 덮어 쓰는 방식으로 동작한다.(참고로 base.py는 settings.py의 명칭을 변경한 것이다.) 기존의 base.py 내용의 일부는 다음과 같았다.

base.py

SECRET_KEY = None # local.py와 prod.py에 각각 서로 다른 SECRET_KEY 값을 지정했다.

...

SIMPLE_JWT = {
     'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
     'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
     'ROTATE_REFRESH_TOKENS': True,
     'BLACKLIST_AFTER_ROTATION': True,
     'UPDATE_LAST_LOGIN': True,

     'ALGORITHM': 'HS256',
     'SIGNING_KEY': SECRET_KEY,
     ...

결론은 base.py의 SECRET_KEY 값이 None이였고, 자연스럽게 SIGNING_KEY 값에도 None 값이 할당 되었기 때문에 발생한 문제였다. 따라서 SIMPLE_JWT을 local.py와 prod.py 각각에 작성 후 base.py에 작성 했던 내용을 없앰으로써 해당 문제를 해결할 수 있었다.

profile
안녕하세요! 질문과 피드백은 언제든지 환영입니다:)

0개의 댓글