关于使用HttpOnly Cookie的jwt:django-rest-framework

django-rest-framework using HttpOnly Cookie

在以不安全的方式使用djangorestframework-jwt一年后,我终于决定我希望以一种更安全的方式使其工作。

我到处都读到了在本地客户端(例如本地存储)中保存JWT令牌的方法,这是不好的,最好的解决方案是改用HttpOnly cookie。

我了解到HttpOnly cookie确实是一个cookie,可以保存但不能被浏览器读取。所以我认为它可以像下面这样使用:

  • get_token:客户端通过发送用户名和密码来向服务器请求授权令牌:如果用户名和密码有效,则服务器将以httpOnly cookie进行响应,该cookie可以存储但不能被客户端读取。
  • 从现在起,客户端执行的每个请求都将得到授权,因为HttpOnly cookie内有一个有效的授权令牌。
  • refresh_token:客户端需要刷新令牌后,只需要请求refresh_token:如果发送的cookie包含有效令牌,则服务器将使用新令牌响应更新的HttpOnly cookie。

我现在正尝试通过使用HttpOnly cookie来使用djangorestframework-jwt,并且JWT_AUTH_COOKIE配置似乎是最合适的配置:

You can set JWT_AUTH_COOKIE a string if you want to use http cookies in addition to the Authorization header as a valid transport for the token. The string you set here will be used as the cookie name that will be set in the response headers when requesting a token. The token validation procedure will also look into this cookie, if set. The 'Authorization' header takes precedence if both the header and the cookie are present in the request.

Default is None and no cookie is set when creating tokens nor accepted when validating them.

在将字符串值赋予JWT_AUTH_COOKIE之后,我收到了预期的httpOnly cookie。

问题:

当我调用refreshToken时,得到以下响应:

1
{"token":["This field is required."]}

是的,我没有在请求的HEADER中发送任何令牌,这就是我想要的,因为客户端不应该将其保存在任何地方。

这就是我感到困惑的地方:

如果从现在起我对客户端对服务器的每个请求都没错,则应将cookie添加到该请求中。

服务器在看到Header中没有传递令牌后,是否应该检查cookie?如果不是这样,应该如何工作?

如果有人想为改进做出贡献,也请在此处发布Github问题:https://github.com/jpadilla/django-rest-framework-jwt/issues/482


您观察到的问题是正确的,因为cookie尚未实现刷新令牌api。

这可能是代码本身的错误。但是没有什么可以限制您解决此问题。

您也可以修补视图以同时处理基于cookie的身份验证。将以下代码添加到urls.py的顶部,它将处理相同的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from rest_framework_jwt.settings import api_settings

if api_settings.JWT_AUTH_COOKIE:
    from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    from rest_framework_jwt.serializers import RefreshJSONWebTokenSerializer
    from rest_framework_jwt.views import RefreshJSONWebToken

    RefreshJSONWebTokenSerializer._declared_fields.pop('token')

    class RefreshJSONWebTokenSerializerCookieBased(RefreshJSONWebTokenSerializer):
        def validate(self, attrs):
            if 'token' not in attrs:
                if api_settings.JWT_AUTH_COOKIE:
                    attrs['token'] = JSONWebTokenAuthentication().get_jwt_value(self.context['request'])
            return super(RefreshJSONWebTokenSerializerCookieBased, self).validate(attrs)

    RefreshJSONWebToken.serializer_class = RefreshJSONWebTokenSerializerCookieBased

Refresh


我已将此中间件添加到我的Django(3.1)中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class YankTokenRefreshFromHeaderIntoTheBody(MiddlewareMixin):
   """
    for Django Rest Framework JWT's POST"/token-refresh" endpoint --- check for a 'token' in the request.COOKIES
    and if, add it to the body payload.
   """

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        return response

    def process_view(self, request, view_func, *view_args, **view_kwargs):
        if request.path == '/v1/token-refresh' and 'token' in request.COOKIES:
            data = json.loads(request.body)
            data['token'] = request.COOKIES['token']
            request._body = json.dumps(data).encode('utf-8')
        return None

然后我在settings中添加了它:

1
2
3
4
5
MIDDLEWARE = [
    'myproj.utils.middleware.YankTokenRefreshFromHeaderIntoTheBody',
    ...
    ...
]

就是这样。 Django REST框架JWT的令牌刷新端点现在可以正常工作,因为它将在其中找到"令牌"键/值。

注意事项:

  • 我选择'token'作为持有tte JWT令牌的cookie的名称。您的课程可能会有所不同。
  • 我将端点的名称更改为/v1/token-refresh -如果您使用的是原始命名的端点,则也需要将其更改。