【问题标题】:How to store JWT tokens in HttpOnly cookies with DRF djangorestframework-simplejwt package?如何使用 DRF djangorestframework-simplejwt 包将 JWT 令牌存储在 HttpOnly cookie 中?
【发布时间】:2021-05-20 16:45:47
【问题描述】:

我已经使用djangorestframework-simplejwt 有一段时间了,现在我想将 JWT 存储在 cookie 中(而不是本地存储或前端状态),以便客户端发出的每个请求都包含令牌。

因此对其进行了一些研究,我发现最相关的结果是this stackoverflow question,其中作者正在使用djangorestframework-jwt 包,该包具有名为JWT_AUTH_COOKIE 的cookie 预配置设置。所以想切换到那个包,但最后发现the package is pretty much dead

虽然建议使用 djangorestframework-jwtfork 来代替,但我想知道是否可以在 HttpOnly cookie 中使用 djagnorestframework_simplejwt 本身设置 JWT?

【问题讨论】:

    标签: django django-rest-framework django-rest-framework-jwt django-rest-framework-simplejwt


    【解决方案1】:

    您可以执行以下操作将刷新令牌存储在 httpOnly cookie 中:

    将此添加到views.py:

    # views.py
    from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView
    from rest_framework_simplejwt.serializers import TokenRefreshSerializer
    from rest_framework_simplejwt.exceptions import InvalidToken
    
    class CookieTokenRefreshSerializer(TokenRefreshSerializer):
        refresh = None
        def validate(self, attrs):
            attrs['refresh'] = self.context['request'].COOKIES.get('refresh_token')
            if attrs['refresh']:
                return super().validate(attrs)
            else:
                raise InvalidToken('No valid token found in cookie \'refresh_token\'')
    
    class CookieTokenObtainPairView(TokenObtainPairView):
      def finalize_response(self, request, response, *args, **kwargs):
        if response.data.get('refresh'):
            cookie_max_age = 3600 * 24 * 14 # 14 days
            response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True )
            del response.data['refresh']
        return super().finalize_response(request, response, *args, **kwargs)
    
    class CookieTokenRefreshView(TokenRefreshView):
        def finalize_response(self, request, response, *args, **kwargs):
            if response.data.get('refresh'):
                cookie_max_age = 3600 * 24 * 14 # 14 days
                response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True )
                del response.data['refresh']
            return super().finalize_response(request, response, *args, **kwargs)
        serializer_class = CookieTokenRefreshSerializer
    

    更改 url.py 中的 url 以使用这些视图获取和刷新令牌:

    # url.py
    from .views import CookieTokenRefreshView, CookieTokenObtainPairView # Import the above views
    # [...]
    urlpatterns = [
        path('auth/token/', CookieTokenObtainPairView.as_view(), name='token_obtain_pair'),
        path('auth/token/refresh/', CookieTokenRefreshView.as_view(), name='token_refresh'),
        # [...]
    ]
    

    检查您的 CORS 设置,如果它没有按预期工作:也许您必须在 set_cookie 中设置 sameSite 和 secure

    工作流程 - 使用凭据获取令牌对

    1. 使用有效凭据发布 /auth/token
    2. 在响应正文中,您会注意到仅设置了“访问”键
    3. “刷新”键已移至名为“刷新令牌”的 httpOnly cookie

    工作流程 - 使用刷新令牌获取访问(和可选刷新)令牌

    1. POST /auth/token/refresh 使用之前设置的 cookie 工作流,body 可以为空

    2. 在响应正文中,您会注意到只设置了“访问”键

    3. 如果您设置了 ROTATE_REFRESH_TOKENS,httpOnly cookie 'refresh_token' 包含一个新的刷新令牌

    参考:https://github.com/jazzband/djangorestframework-simplejwt/issues/71#issuecomment-762927394

    【讨论】:

      【解决方案2】:

      使用 httponly cookie 标志和 CSRF 保护遵循此代码。

      在移动应用和网络应用中都非常有用..

      urls.py:

      ...
      path('login/',LoginView.as_view(),name = "login"),
      ...
      

      view.py:

      from rest_framework_simplejwt.tokens import RefreshToken
      from django.middleware import csrf
      
      def get_tokens_for_user(user):
          refresh = RefreshToken.for_user(user)
              
          return {
              'refresh': str(refresh),
              'access': str(refresh.access_token),
          }
      
      class LoginView(APIView):
          def post(self, request, format=None):
              data = request.data
              response = Response()        
              username = data.get('username', None)
              password = data.get('password', None)
              user = authenticate(username=username, password=password)
              if user is not None:
                  if user.is_active:
                      data = get_tokens_for_user(user)
                      response.set_cookie(
                                          key = settings.SIMPLE_JWT['AUTH_COOKIE'], 
                                          value = data["access"],
                                          expires = settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
                                          secure = settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'],
                                          httponly = settings.SIMPLE_JWT['AUTH_COOKIE_HTTP_ONLY'],
                                          samesite = settings.SIMPLE_JWT['AUTH_COOKIE_SAMESITE']
                                              )
                      csrf.get_token(request)
                      email_template = render_to_string('login_success.html',{"username":user.username})    
                      login = EmailMultiAlternatives(
                          "Successfully Login", 
                          "Successfully Login",
                          settings.EMAIL_HOST_USER, 
                          [user.email],
                      )
                      login.attach_alternative(email_template, 'text/html')
                      login.send()
                      response.data = {"Success" : "Login successfully","data":data}
                      
                      return response
                  else:
                      return Response({"No active" : "This account is not active!!"},status=status.HTTP_404_NOT_FOUND)
              else:
                  return Response({"Invalid" : "Invalid username or password!!"},status=status.HTTP_404_NOT_FOUND)
      

      authenticate.py:

      from rest_framework_simplejwt.authentication import JWTAuthentication
      from django.conf import settings
      
      from rest_framework.authentication import CSRFCheck
      from rest_framework import exceptions
      
      def enforce_csrf(request):
          """
          Enforce CSRF validation.
          """
          check = CSRFCheck()
          # populates request.META['CSRF_COOKIE'], which is used in process_view()
          check.process_request(request)
          reason = check.process_view(request, None, (), {})
          if reason:
              # CSRF failed, bail with explicit error message
              raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
      
      class CustomAuthentication(JWTAuthentication):
          
          def authenticate(self, request):
              header = self.get_header(request)
              
              if header is None:
                  raw_token = request.COOKIES.get(settings.SIMPLE_JWT['AUTH_COOKIE']) or None
              else:
                  raw_token = self.get_raw_token(header)
              if raw_token is None:
                  return None
      
              validated_token = self.get_validated_token(raw_token)
              enforce_csrf(request)
              return self.get_user(validated_token), validated_token
      

      settings.py:

      ....
      REST_FRAMEWORK = {
          'DEFAULT_AUTHENTICATION_CLASSES': (
              'authentication.authenticate.CustomAuthentication',
          ),
      }
      
      SIMPLE_JWT = {
      .....
      'AUTH_COOKIE': 'access_token',  # Cookie name. Enables cookies if value is set.
      'AUTH_COOKIE_DOMAIN': None,     # A string like "example.com", or None for standard domain cookie.
      'AUTH_COOKIE_SECURE': False,    # Whether the auth cookies should be secure (https:// only).
      'AUTH_COOKIE_HTTP_ONLY' : True, # Http only cookie flag.It's not fetch by javascript.
      'AUTH_COOKIE_PATH': '/',        # The path of the auth cookie.
      'AUTH_COOKIE_SAMESITE': 'Lax',  # Whether to set the flag restricting cookie leaks on cross-site requests.
                                      # This can be 'Lax', 'Strict', or None to disable the flag.
      }
      

      --------- 或 ------------

      通过使用 middleware.py:

      How to authenticate by using middleware

      必须:

      withCredentials 对双方都是 True..

      有任何疑问请评论..

      【讨论】:

      • 在views.py,秒行,我不知道从哪里得到RefreshTokenGrantrest_framework_simplejwt.tokensRefreshToken,你是这个意思吗?还是自定义方法?
      • 是的,RefreshToken。当时我在复制此代码时,我的VS code editor sn-p 更改了它。我不知道这个更改是什么时候发生的。Tnx @Jalal。跨度>
      • 哦,好伙伴,tnq。现在再次在views.py 中,上面写着user = authenticate(username=username, password=password)authenticate 我们是在调用序列化程序还是在调用我们在CustomAuthentication 类的另一个模块中创建的自定义认证方法?
      • user = authenticate(username=username, password=password) 此身份验证来自from django.contrib.auth import authenticate 内部CustomAuthentication 继承自JWTAuthentication。仅检查JWT 令牌是否有效。如果此令牌有效,则用户可以执行特定任务。否则不提供身份验证凭据....
      • @Pradip 嘿,代替这个函数RefreshToken 为用户生成令牌,是否可以添加自定义方法?或者这是最安全的方法。谢谢
      猜你喜欢
      • 2021-02-11
      • 2021-10-16
      • 2021-05-18
      • 1970-01-01
      • 2021-06-18
      • 2020-12-09
      • 1970-01-01
      • 2020-10-06
      • 2015-09-15
      相关资源
      最近更新 更多