处理此问题的推荐方法是将令牌新鲜度与令牌验证分开。大多数视图需要一个有效的令牌,安全视图需要一个新的令牌,这个令牌不仅有效,而且在登录时已经发出,此后一直没有刷新。
您可以通过在令牌上设置一个标志以在登录时将其标记为“新鲜”来做到这一点,但在刷新时取消设置该标志。然后流程变为:
- 客户端在没有令牌的情况下访问站点 -> 拒绝访问
- 客户端使用获取令牌端点进行身份验证 -> 发出令牌
fresh=True
- 客户端(或服务器)刷新有效令牌 -> 发出令牌
fresh=False
- 客户端使用有效令牌访问非安全端点 -> 接受令牌
- 客户端访问安全端点 -> 仅当
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,
)
# ...