【问题标题】:Return appropriate error for unactivated users in Djoser为 Djoser 中未激活的用户返回适当的错误
【发布时间】:2021-04-15 21:06:55
【问题描述】:

我正在使用 Django 2.2.14 和 Djoser 2.1.0 的以下配置,但是当尝试为非活动用户获取 JWT 令牌时,它返回与使用错误密码相同的错误,这使得区分变得棘手。 我得到 HTTP STATUS 401 的详细信息如下

{ "detail": "No active account found with the given credentials }

我的配置Djoser如下图:

 'LOGIN_FIELD': 'email', 
'SEND_CONFIRMATION_EMAIL': True, 
'PASSWORD_CHANGED_EMAIL_CONFIRMATION': True, 
'USER_CREATE_PASSWORD_RETYPE': True, 
'TOKEN_MODEL': None,  
'SEND_ACTIVATION_EMAIL': True, 
"LOGOUT_ON_PASSWORD_CHANGE": False,  
"PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND": True,  
"USERNAME_RESET_SHOW_EMAIL_NOT_FOUND": True,  
'PASSWORD_RESET_CONFIRM_URL': 'account/password/reset/confirm/{uid}/{token}',  
'USERNAME_RESET_CONFIRM_URL': 'account/username/reset/  /{uid}/{token}', 
'ACTIVATION_URL': 'account/activate/{uid}/{token}', 

我也在用AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']

【问题讨论】:

    标签: python django django-rest-framework djoser


    【解决方案1】:

    经过一番挖掘,发现 Djoser 使用 simple-jwt 模块生成其 JWT 令牌,因此错误消息来自该模块。

    在一些为 simple-jwt 做出贡献的人的帮助下,我能够修改如下所示的代码,以使端点检查用户是否处于非活动状态并发送适当的错误。

    # custom_serializers.py
    from django.contrib.auth.models import update_last_login
    from rest_framework_simplejwt.serializers import TokenObtainSerializer
    from rest_framework_simplejwt.exceptions import AuthenticationFailed
    from rest_framework import status
    from rest_framework_simplejwt.settings import api_settings
    from rest_framework_simplejwt.tokens import RefreshToken
    
    
    class InActiveUser(AuthenticationFailed):
        status_code = status.HTTP_406_NOT_ACCEPTABLE
        default_detail = "User is not active, please confirm your email"
        default_code = 'user_is_inactive'
    
    
    # noinspection PyAbstractClass
    class CustomTokenObtainPairSerializer(TokenObtainSerializer):
    
        @classmethod
        def get_token(cls, user):
            return RefreshToken.for_user(user)
    
        def validate(self, attrs):
            data = super().validate(attrs)
            if not self.user.is_active:
                raise InActiveUser()
    
            refresh = self.get_token(self.user)
    
            data['refresh'] = str(refresh)
            data['access'] = str(refresh.access_token)
    
            if api_settings.UPDATE_LAST_LOGIN:
                update_last_login(None, self.user)
    
            return data
    
    # custom_authentication.py
    def custom_user_authentication_rule(user):
        """
        Override the default user authentication rule for Simple JWT Token to return true if there is a user and let
        serializer check whether user is active or not to return an appropriate error.
    Add 'USER_AUTHENTICATION_RULE': 'path_to_custom_user_authentication_rule' to simplejwt settings to override the default.
        :param user: user to be authenticated
        :return: True if user is not None
        """
    
        return True if user is not None else False
    
    # views.py
    from .custom_serializer import CustomTokenObtainPairSerializer, InActiveUser
    from rest_framework.response import Response
    from rest_framework_simplejwt.exceptions import AuthenticationFailed, InvalidToken, TokenError
    from rest_framework_simplejwt.views import TokenViewBase
    
    class CustomTokenObtainPairView(TokenViewBase):
        """
        Takes a set of user credentials and returns an access and refresh JSON web
        token pair to prove the authentication of those credentials.
    
        Returns HTTP 406 when user is inactive and HTTP 401 when login credentials are invalid.
        """
        serializer_class = CustomTokenObtainPairSerializer
    
        def post(self, request, *args, **kwargs):
            serializer = self.get_serializer(data=request.data)
            try:
                serializer.is_valid(raise_exception=True)
            except AuthenticationFailed:
                raise InActiveUser()
            except TokenError:
                raise InvalidToken()
    
            return Response(serializer.validated_data, status=status.HTTP_200_OK)
    
    # urls.py
     path('api/token/', CustomTokenObtainPairView.as_view(),
             name='token_obtain_pair'),
     path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
     path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
    

    【讨论】:

    • 这很好,但可能还需要自定义 authentication backend 才能使其正常工作。另请参阅自定义模型后端上的 answer
    • 如果用户处于非活动状态,默认身份验证返回 None。上述评论的原因。
    猜你喜欢
    • 2019-04-19
    • 1970-01-01
    • 2020-03-27
    • 2020-04-07
    • 1970-01-01
    • 2018-04-19
    • 2020-10-30
    • 2020-12-27
    • 2015-12-01
    相关资源
    最近更新 更多