7

场景:我希望用户在N分钟后通过安全敏感区域时重新登录,例如当用户即将支付订单时,但他在1小时前登录,我想确定是他。这通过使用rest_framework_jwt.

详细描述:

我最近一直在测试django 现代 Web 开发(所以,后端使用 rest-api)。但是,我遇到了一个尚未找到解决方案的问题。

rest_framework_jwt您设置身份验证类如下。

'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework_jwt.authentication.JSONWebTokenAuthentication',

这将为通用目的做一个伟大的工作。但是,我希望用户在登录后 10 分钟进入带有敏感信息的区域时重新识别(重新登录),例如敏感信息可以是支付区域。因此,我想向 Authentication 类发送一个参数,告诉用户在一个合理的区域。

我认为可能的解决方案,但我还不知道该怎么做,是: The在使用 option 时rest_framework_jwt创建变量。我可以向身份验证类发送一个标志来告诉当前视图是否是敏感区域,如果是这样并且用户在 10 多分钟前登录,我可以发送一条消息说用户需要重新登录才能继续.orig_iatJWT_ALLOW_REFRESH

我不介意分叉rest_framework_jwt项目并根据我的目的对其进行调整,但是我想知道如何将参数从视图发送到身份验证类(在本例中为:)rest_framework_jwt.authentication.JSONWebTokenAuthentication

另外,如果已经rest_framework_jwt为这种情况做了一些事情,我想避免重新发明轮子。

4

3 回答 3

0

嗯......到目前为止,我所做的是为函数视图创建一个装饰器。装饰器的代码如下:

from functools import wraps
from rest_framework_jwt.settings import api_settings
from django.utils.translation import ugettext as _
from calendar import timegm
import datetime
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
def recently_authenticated():
    def decorator(func):
        @wraps(func)
        def inner(request, *args, **kwargs):
            jwt_payload = jwt_decode_handler(request._auth)
            rencent_auth_limit = api_settings.JWT_RECENT_AUTHENTICATION_DELTA
            if isinstance(rencent_auth_limit, datetime.timedelta):
                rencent_auth_limit = (rencent_auth_limit.days * 24 * 3600 +
                                 rencent_auth_limit.seconds) + jwt_payload["orig_iat"]
            timenow = timegm(datetime.datetime.utcnow().utctimetuple())
            if timenow>rencent_auth_limit:
                return Response({"detail":_("you have to reidentify to enter this area")},
                                status=401)
            return func(request, *args, **kwargs)
        return inner
    return decorator

响应格式以与 相同的格式给出rest_framework_jwt.authentication.JSONWebTokenAuthentication。常量是插入到包(fork)JWT_RECENT_AUTHENTICATION_DELTA中的临时参数。settings.pyrest_framework_jwt

最后,为了使用它,可以将装饰器添加到任何视图。例如:

@api_view()
@recently_authenticated()
def index_view(request):
    data = User.objects.filter()
    return Response(UserSerializer(data, many=True).data)

并且当用户不久前通过身份验证时,它将发送{"detail":"you have to reidentify to enter this area"}带有代码的消息401。这可以由前端评估和解析,并将用户重定向到登录。

注意:装饰器只评估时间和经过的时间。验证告诉它是否是正确的用户和正确的令牌仍然由rest_framework_jwt.authentication.JSONWebTokenAuthentication.

于 2018-10-29T21:30:21.677 回答
0

根据手册:https ://getblimp.github.io/django-rest-framework-jwt/#additional-settings

禁止刷新令牌应该可以完成这项工作。问题是您只会获得 1 个令牌,并且 1 小时后您将无法刷新它。

JWT_AUTH = {
    'JWT_ALLOW_REFRESH': False,
    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(hours=1),
}

其他问题应该在前端解决。您应该检查用户是否试图进入“敏感”视图。如果是,则检查令牌是否有效。如果无效 - 重定向到登录页面。如果视图是“不敏感的” - 你的选择。

于 2018-10-31T16:31:52.427 回答
0

处理此问题的推荐方法是将令牌新鲜度与令牌验证分开。大多数视图需要一个有效的令牌,安全视图需要一个的令牌,这个令牌不仅有效,而且在登录时已经发出,此后一直没有刷新。

您可以通过在令牌上设置一个标志以在登录时将其标记为“新鲜”来做到这一点,但在刷新时取消设置该标志。然后流程变为:

  1. 客户端在没有令牌的情况下访问站点 -> 拒绝访问
  2. 客户端使用获取令牌端点进行身份验证 -> 发出令牌fresh=True
  3. 客户端(或服务器)刷新有效令牌 -> 发出令牌fresh=False
  4. 客户端使用有效令牌访问非安全端点 -> 接受令牌
  5. 客户端访问安全端点 -> 仅当fresh=True在令牌上设置时才接受令牌。

获得新令牌的唯一方法是再次登录,不允许刷新。

所以你需要能够:

  • 在生成 JWT 有效负载时区分获取新令牌和刷新。
  • 检查fresh当前 JWT 令牌中的密钥以创建自定义权限

第一个要求意味着您必须进行一些视图子类化,因为jwt_payload_handler回调没有提供任何信息来确定调用它的内容。

处理第一个要求的最简单方法是对用于生成新令牌或刷新令牌的序列化程序进行子类化,解码它们生成的令牌,注入适用的fresh键值,然后重新编码。然后使用子类序列化器创建一组新的 API 视图

from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.serializers import JSONWebTokenSerializer, RefreshJSONWebTokenSerializer
from rest_framework_jwt.views import JSONWebTokenAPIView

jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER

class FreshJSONWebTokenSerializer(JSONWebTokenSerializer):
    """Add a 'fresh=True' flag to the JWT token when issuing"""

    def validate(self, *args, **kwargs):
        result = super().validate(*args, **kwargs)
        payload = jwt_decode_handler(result['token'])
        return {
            **result,
            'token': jwt_encode_handler({**payload, fresh=True})
        }

class NonFreshRefreshJSONWebTokenSerializer(RefreshJSONWebTokenSerializer):
    """Set the 'fresh' flag False on refresh"""

    def validate(self, *args, **kwargs):
        result = super().validate(*args, **kwargs)
        payload = jwt_decode_handler(result['token'])
        return {
            **result,
            'token': jwt_encode_handler({**payload, fresh=False})
        }

class ObtainFreshJSONWebToken(JSONWebTokenAPIView):
    serializer_class = JSONWebTokenSerializer

class NonFreshRefreshJSONWebToken(JSONWebTokenAPIView):
    serializer_class = NonFreshRefreshJSONWebTokenSerializer

obtain_jwt_token = ObtainFreshJSONWebToken.as_view()
refresh_jwt_token = NonFreshRefreshJSONWebToken.as_view()

然后将这两个视图注册为 API 端点,而不是Django REST Framework JWT 项目提供的那些,用于获取刷新路径。

接下来是权限;因为当验证属性设置为有效负载字典时,JSONWebTokenAuthentication该类返回解码的有效负载,让我们直接在自定义权限中检查它:request.auth

class HashFreshTokenPermission(permissions.BasePermission):
    message = 'This endpoint requires a fresh token, please obtain a new token.'

    def has_permission(self, request, view):
        return (
            request.user and
            request.user.is_authenticated and
            request.auth and
            isinstance(request.auth, dict) and
            request.auth.get('fresh', False)
        )

向 REST 框架注册此权限:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
        'yourmodule.HashFreshTokenPermission',
    ),
    # ...
}

并在安全敏感的视图中使用它:

class SampleSecureView(APIView):
    permission_classes = (
        permissions.IsAuthenticated,
        HashFreshTokenPermission,
    )
    # ...
于 2018-11-06T13:32:29.220 回答