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

#django #django-rest-framework #jwt #django-authentication #django-contrib

Вопрос:

Я использую 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),

}
 

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

 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)
 

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

Ответ №1:

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

У обоих в основном есть эти взгляды:

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

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

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

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

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

Комментарии:

1. Спасибо за заметку об устаревшей библиотеке. Что касается «После достижения, даже если вы активны, вам нужно будет снова пройти аутентификацию», это не то, чего я хочу. Я хочу, чтобы сеанс истекал после одного часа бездействия. Если пользователь продолжает делать запросы к допустимым конечным точкам, я хочу, чтобы сеанс продолжался без необходимости входа в систему. Я бы предпочел не отправлять два токена в заголовке «Авторизация» для каждого запроса, но, думаю, я открыт, если этого абсолютно невозможно достичь.

Ответ №2:

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

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

И клиент будет использовать этот недавно выпущенный токен.
для этого вы можете добавить промежуточное программное обеспечение django:
**ОТРЕДАКТИРОВАНО

 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_authorization_header(request).decode('utf-8')
        new_jwt_token = None
        try:
            payload = jwt.decode(jwt_token, settings.SECRET_KEY)
            new_jwt_token = JWT_ENCODE_HANDLER(payload)
        except  PyJWTError:
            pass
            
        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.
        if new_jwt_token:
            response['Refresh-Token'] = new_jwt_token

        return response
 

И клиент должен проверить заголовок «Обновить токен» при ответе, и если он есть, он должен заменить токен и использовать недавно выпущенный токен с увеличенным сроком службы.
примечание: лучше регулировать выдачу новых токенов, например, каждый раз, когда срок действия токена запроса истекает в течение следующих 20 минут…

Комментарии:

1. Являются ли функции «get_token_from_reques» и «check_jwt_is_valid», которые я бы написал, или они предоставляются платформой Django REST?

2. @Дейв, это были метафоры, и вы должны их написать, для «get_token_from_request» вы использовали «get_authorization_header» раньше, а для «check_jwt_is_valid» вы использовали «jwt.decode»

3. Я отредактировал ответ, так что теперь он использует реальные функции :)))