Как использовать фреймворк для отдыха Django для продления токена сессии JWT?

Я использую Django 3.2 с приложением django.auth.contrib и djangorestframework-jwt==1.11.0. Как мне продлить/выпустить новый токен сессии после получения запроса на аутентифицированный ресурс и проверки того, что пользователь может получить доступ к этому ресурсу? Я использую следующий сериализатор и представление для авторизации пользователя и выдачи начального токена

class UserLoginSerializer(serializers.Serializer):

    username = serializers.CharField(max_length=255)
    password = serializers.CharField(max_length=128, write_only=True)
    token = serializers.CharField(max_length=255, read_only=True)

    def validate(self, data):
        username = data.get("username", None)
        password = data.get("password", None)
        user = authenticate(username=username, password=password)
        if user is None:
            raise serializers.ValidationError(
                'A user with this email and password is not found.'
            )
        try:
            payload = JWT_PAYLOAD_HANDLER(user)
            jwt_token = JWT_ENCODE_HANDLER(payload)
            update_last_login(None, user)
        except User.DoesNotExist:
            raise serializers.ValidationError(
                'User with given email and password does not exists'
            )
        return {
            'username':user.username,
            'token': jwt_token
        }

class UserLoginView(RetrieveAPIView):

    permission_classes = (AllowAny,)
    serializer_class = UserLoginSerializer

    def post(self, request):
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)
        response = {
            'success' : 'True',
            'status code' : status.HTTP_200_OK,
            'message': 'User logged in successfully',
            'token' : serializer.data['token'],
            }
        status_code = status.HTTP_200_OK

        return Response(response, status=status_code)

У меня в файле настроек есть это, чтобы изначально сессия длилась 1 час

JWT_AUTH = {
    # how long the original token is valid for
    'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=1),

}

Клиент передает токен сессии в заголовке "Authorization", и он проверяется (например) с помощью приведенного ниже представления

class UserProfileView(RetrieveAPIView):

    permission_classes = (IsAuthenticated,)
    authentication_class = JSONWebTokenAuthentication

    def get(self, request):
        try:
            token = get_authorization_header(request).decode('utf-8')
            if token is None or token == "null" or token.strip() == "":
                raise exceptions.AuthenticationFailed('Authorization Header or Token is missing on Request Headers')
            decoded = jwt.decode(token, settings.SECRET_KEY)
            username = decoded['username']
            
            status_code = status.HTTP_200_OK
            response = {
                'success': 'true',
                'status code': status_code,
                'message': 'User profile fetched successfully',
                'data': {
                        #...
                    }
                }

        except Exception as e:
            status_code = status.HTTP_400_BAD_REQUEST
            response = {
                'success': 'false',
                'status code': status.HTTP_400_BAD_REQUEST,
                'message': 'User does not exists',
                'error': str(e)
                }
        return Response(response, status=status_code)

В своем ответе я хотел бы отправить пользователю новый токен сессии, который будет действителен в течение часа, но мне неясно, какой вызов мне нужно сделать, чтобы сгенерировать такой токен и/или отредактировать/аннулировать существующий.

Во-первых, я бы рекомендовал предпочесть djangorestframework-simplejwt вместо django-rest-framework-jwt (который не поддерживается).

В основном оба придерживаются таких взглядов:

  • Получение представления токена (т.е. логин), принимает учетные данные и возвращает пару токенов доступа и обновления
  • Refresh token view, принимает действительный токен обновления и возвращает обновленный токен доступа

У вас будет два разных времени жизни для ваших токенов. Ваш маркер доступа обычно живет несколько минут, в то время как ваш маркер обновления будет действовать столько, сколько вы хотите, чтобы ваша сессия была действительна.

Токен доступа используется для подтверждения вашей аутентификации. Когда срок его действия истекает, вы должны запросить новый благодаря представлению refresh. Если ваш маркер обновления недействителен (истек или занесен в черный список), вы можете стереть состояние аутентификации на вашем клиенте и запросить учетные данные снова, чтобы получить новую пару.

По умолчанию, когда вы проходите аутентификацию, у вас будет токен обновления, действительный до истечения определенного срока. По его истечении, даже если вы активны, вам нужно будет снова пройти аутентификацию.

Если вам нужна недолговечная сессия, вы, возможно, захотите имитировать SESSION_SAVE_EVERY_REQUEST в Django, чтобы отложить истечение срока действия сессии. Вы можете добиться этого, чередуя токены обновления: когда вы запрашиваете новый токен для вашего представления обновления, оно выдает как обновленные токены доступа, так и токены обновления, и срок действия токена обновления будет отложен. Это покрывается djangorestframework-simplejwt благодаря настройке ROTATE_REFRESH_TOKENS.

Невозможно изменить JWT после его выдачи, поэтому вы не можете продлить срок его жизни, но вы можете сделать что-то вроде этого:

for every request client makes:
    if JWT is expiring:
       generate a new JWT and add it to the response

И клиент будет использовать этот только что выпущенный токен.
Для этого вы можете добавить промежуточное программное обеспечение django middleware:

class ExtendJWTToResponse:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        
        jwt_token = get_token_from_request(request)
        if check_jwt_is_valid(jwt_token, your_signing_key):
            payload = get_jwt_payload(jwt_token)
            new_jwt_token = JWT_ENCODE_HANDLER(payload)
            
        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        response['Refresh-Token'] = new_jwt_token

        return response

А клиент должен проверить наличие заголовка 'Refresh-Token' в ответе и, если он есть, заменить токен и использовать вновь выпущенный токен с увеличенным временем жизни.
Примечание: лучше дросселировать выдачу новых токенов, например, каждый раз, когда срок действия токена запроса истекает в ближайшие 20 минут...

Вернуться на верх