[Django][Web] Django의 simple-jwt를 이용한 로그인 기능 구현(2)

이수진·2022년 1월 6일
0
post-custom-banner

simple-jwt에 대한 두번째입니다!
오늘은 받은 access, refresh토큰 기반으로 유저를 식별하는 방법에 대해 소개해드릴려고 합니다.
즉, 로그인이 되어있다고 가정했을때 서버에서 토큰을 기반으로 유저의 정보를 식별해내는 것입니다.

먼저, 제 코드는 다음과 같습니다.

        try:
            access_token = request.COOKIES['access_token']
            payload = jwt.decode(access_token, env('DJANGO_SECRET_KEY'), algorithms=['HS256'])
            pk = payload.get('user_id')
            user = get_user(pk)
            serializer = UserSeriallizer(user)
            response = Response(
                serializer.data,
                status=status.HTTP_200_OK
            )
            response.set_cookie('access_token', access_token)
            response.set_cookie('refresh_token', request.COOKIES['refresh_token'])
            return response

        # 토큰 만료시 토큰 갱신
        except(jwt.exceptions.ExpiredSignatureError):
            try:
                # access 토큰 만료시
                serializer = TokenRefreshSerializer(data={'refresh': request.COOKIES.get('refresh_token', None)})

                if serializer.is_valid(raise_exception=True):
                    access_token = serializer.validated_data['access']
                    refresh_token = request.COOKIES.get('refresh_token', None)
                    payload = jwt.decode(access_token, env('DJANGO_SECRET_KEY'), algorithms=['HS256'])
                    pk = payload.get('user_id')
                    user = get_user(pk)
                    serializer = UserSeriallizer(instance=user)
                    response = Response(serializer.data, status=status.HTTP_200_OK)
                    response.set_cookie('access_token', access_token)
                    response.set_cookie('refresh_token', refresh_token)
                    return response
            except(rest_framework_simplejwt.exceptions.TokenError): # refresh 토큰까지 만료 시
                return Response({"message": "로그인이 만료되었습니다."}, status=status.HTTP_200_OK)

            raise jwt.exceptions.InvalidTokenError

        except(jwt.exceptions.InvalidTokenError): # 토큰 invalid 인 모든 경우
            return Response({"message": "로그인이 만료되었습니다."}, status=status.HTTP_200_OK)

나누면 세 가지 경우가 있더라구요.

  • access토큰이 유효한 경우 -> access토큰으로 유저 정보 식별
  • access토큰 만료 시 -> refresh토큰을 이용하여 새로운 access토큰 발급 + 이를 이용한 유저 정보 식별
  • refresh토큰까지 만료 시 + 그 외 토큰 에러에 대한 예외 -> 로그인 만료로 처리

이렇게 나누어서 로직을 짰구요,
여기서 소개해드릴 내장 함수는 TokenRefreshSerializer 입니다.
이는 바로 두번째 경우에서 이용되었구요,
access토큰이 만료되었을 경우에 refresh토큰을 이용하여 새로운 access토큰을 발급해주는 serializer입니다!

이를 까보면 다음과 같습니다.

class TokenRefreshSerializer(serializers.Serializer):
    refresh = serializers.CharField()
    access = serializers.CharField(read_only=True)

    def validate(self, attrs):
        refresh = RefreshToken(attrs['refresh'])

        data = {'access': str(refresh.access_token)}

        if api_settings.ROTATE_REFRESH_TOKENS:
            if api_settings.BLACKLIST_AFTER_ROTATION:
                try:
                    # Attempt to blacklist the given refresh token
                    refresh.blacklist()
                except AttributeError:
                    # If blacklist app not installed, `blacklist` method will
                    # not be present
                    pass

            refresh.set_jti()
            refresh.set_exp()
            refresh.set_iat()

            data['refresh'] = str(refresh)

        return data

즉, 이는 전달받은 refresh토큰을 기반으로 refresh.access_token 을 수행하여,
새로운 access토큰을 발행하고 이를 반환합니다!
그래서 이를 수행한 후,

access_token = serializer.validated_data['access']

를 수행하여 새로 발급받은 access_token을 가져올 수 있습니다!!

이 TokenRefreshSerializer에서 이 access값을 가져올 때,
그동안 serializer.data를 이용해 값을 가져올 수 있어서 계속 이렇게 썼더니 계속 오류가 났었습니다 ㅠㅠㅠ

계속 까보고 내장함수들 일일이 체크하여서 validated_data로만 access토큰의 값을 가져올 수 있었습니다! ㅜㅜ
여기서는 .data 메소드를 지원하지 않는것 같더라구요
무조건 validated_data로만 access토큰값을 가져올 수 있었습니다.

그리고 번외로 아직까지 해결되지 않은 의문점이 있었는데요
이를 수행한 후 refresh값도 분명히 data['refresh'] = str(refresh) 로 data에 담겨 있는데,
이 refresh값은 못가져오더라구요 ... ?
access토큰은 가져왔는데, 이 값은 못가져왔습니다..
물론 여기서는 갱신된 access토큰의 값만 필요했지만, 좀 기분이 찝찝하더라구요

혹시 TokenRefreshSerializer에서 refresh토큰값은 어떻게 가져오는지 아신다면,, 저도 댓글로 알려주세요 ㅎㅎ

아무튼 ! 이렇게 해서 저는 로직을 완성했습니다.

이렇게 유저의 정보를 식별할때마다 쓰이는 이 부분은 아예 따로 함수로 빼서 따로 디렉토리를 만들고 파일을 만들어 따로 저장한 후 필요할때마다 import 해서 쓰는 것이 좋은 것 같아요
저도 따로 빼서 필요할때마다 호출하는 식으로 쓰고 있습니다!!

다음 번은 django의 태그 기능을 제공하는 모듈인 taggit에 대해 소개해드리려구합니다.
저희 서비스에 태그 기능이 있는데, 이를 처음엔 Manytomany 로 구현하려다가,, 정말 너무 좋은 모듈을 발견해서 ㅎㅎ 얼른 까보고 알려드리도록 할게용 !! 😍

profile
꾸준히, 열심히, 그리고 잘하자
post-custom-banner

1개의 댓글

comment-user-thumbnail
2022년 3월 29일

저 코드가 어느 부위에 어느 파일에 어느 클래스에 들어가야하는 걸까요???

답글 달기