如何在“django-rest-framework-simplejwt”中使用'email'代替'username'来生成令牌?

rta7y2nd  于 2022-12-24  发布在  Go
关注(0)|答案(6)|浏览(189)

在django-rest-framework-simplejwt插件中,默认使用usernamepassword。但是我想使用email而不是username。所以,我做了如下:

在序列化程序中:

class MyTokenObtainSerializer(Serializer):
    username_field = User.EMAIL_FIELD

    def __init__(self, *args, **kwargs):
        super(MyTokenObtainSerializer, self).__init__(*args, **kwargs)

        self.fields[self.username_field] = CharField()
        self.fields['password'] = PasswordField()

    def validate(self, attrs):
        # self.user = authenticate(**{
        #     self.username_field: attrs[self.username_field],
        #     'password': attrs['password'],
        # })
        self.user = User.objects.filter(email=attrs[self.username_field]).first()
        print(self.user)

        if not self.user:
            raise ValidationError('The user is not valid.')

        if self.user:
            if not self.user.check_password(attrs['password']):
                raise ValidationError('Incorrect credentials.')
        print(self.user)
        # Prior to Django 1.10, inactive users could be authenticated with the
        # default `ModelBackend`.  As of Django 1.10, the `ModelBackend`
        # prevents inactive users from authenticating.  App designers can still
        # allow inactive users to authenticate by opting for the new
        # `AllowAllUsersModelBackend`.  However, we explicitly prevent inactive
        # users from authenticating to enforce a reasonable policy and provide
        # sensible backwards compatibility with older Django versions.
        if self.user is None or not self.user.is_active:
            raise ValidationError('No active account found with the given credentials')

        return {}

    @classmethod
    def get_token(cls, user):
        raise NotImplemented(
            'Must implement `get_token` method for `MyTokenObtainSerializer` subclasses')

class MyTokenObtainPairSerializer(MyTokenObtainSerializer):
    @classmethod
    def get_token(cls, user):
        return RefreshToken.for_user(user)

    def validate(self, attrs):
        data = super(MyTokenObtainPairSerializer, self).validate(attrs)

        refresh = self.get_token(self.user)

        data['refresh'] = text_type(refresh)
        data['access'] = text_type(refresh.access_token)

        return data

视野中:

class MyTokenObtainPairView(TokenObtainPairView):
   """
    Takes a set of user credentials and returns an access and refresh JSON web
    token pair to prove the authentication of those credentials.
   """
    serializer_class = MyTokenObtainPairSerializer

而且它起作用了!
现在我的问题是,我怎样才能更有效地做这件事?谁能给我一些建议?谢谢。

c90pui9n

c90pui9n1#

  • 此答案供未来读者使用,因此包含额外信息。*

为了简化身份验证后端,您需要连接到多个类。我建议您使用下面的option 1(以及可选的option 3,这是您的简化版本)。在阅读之前,请注意以下几点:

***注1:**django在创建用户时并不强制要求电子邮件是唯一的(你可以覆盖这一点,但这是题外话)!选项3(你的实现)可能会给予你带来重复电子邮件的问题。
***注意1b:**使用User.objects.filter(email__iexact=...)以不区分大小写的方式匹配电子邮件。
***注1c:**使用get_user_model()以防将来替换默认用户模型,这对初学者来说真的是一个救命稻草!
***注意2:**避免将用户打印到控制台。您可能正在打印敏感数据。

对于3个选项:

  • 使用f.e. class EmailModelBackend(ModelBackend)调整django认证后端并替换认证函数。
  • 不调整象征性索赔
  • 不依赖于JWT类/中间件(SimpleJWT、JWT或其他)
  • 还调整其他身份验证类型(会话/Cookie/非API身份验证等)
  • 所需的输入参数仍然是username,示例如下。如果您不喜欢它,请进行调整,但请小心。(可能会破坏您的导入/插件,但不是必需的!)
  • 替换django.contrib.auth中的django authenticate(username=, password=, **kwarg)
  • 不调整象征性索赔
  • 您还需要替换令牌后端,因为它应该使用不同的身份验证,正如您在上面所做的那样。
  • 不使用authenticate(...)调整其他应用程序,仅替换JWT auth(如果您这样设置)参数不是必需的,因此不建议使用此选项)。
  • 按照要求使用电子邮件实施MyTokenObtainPairSerializer
  • 现在,电子邮件作为JWT数据(而不是ID)发回。
  • 与选项1一起,您的应用身份验证将与用户名无关。
    选项1(注意,这也允许用户名!!):
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q

class EmailorUsernameModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        try:
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
        except UserModel.DoesNotExist:
            return None
        else:
            if user.check_password(password):
                return user
        return None

**选项2:**跳过,留给读者,不建议。
**选项3:**您似乎已在上文中涵盖了这一点。
注意:您不必定义MyTokenObtainPairView,您可以在urls.py中使用TokenObtainPairView(serializer_class=MyTokenObtainPairSerializer).as_view()。覆盖所用标记序列化器的小简化。
注2:您可以在www.example.com(或设置文件)中指定标识声明和添加的数据settings.py以使用电子邮件。这将使您的前端应用也使用声明的电子邮件(而不是默认的user.id)

SIMPLE_JWT = {
    'USER_ID_FIELD': 'id', # model property to attempt claims for
    'USER_ID_CLAIM': 'user_id', # actual keyword in token data
}

但是,请注意创作者给出的独特性警告:
例如,指定“用户名”或“电子邮件”字段将是一个糟糕的选择,因为帐户的用户名或电子邮件可能会根据给定服务中帐户管理的设计方式而更改。
如果你能保证唯一性,你就万事俱备了。

6qftjkof

6qftjkof2#

为什么你要复制粘贴这么多东西而不是子类化呢?我用它来工作:

# serializers.py 
from rest_framework_simplejwt.serializers import TokenObtainSerializer

class EmailTokenObtainSerializer(TokenObtainSerializer):
    username_field = User.EMAIL_FIELD

class CustomTokenObtainPairSerializer(EmailTokenObtainSerializer):
    @classmethod
    def get_token(cls, user):
        return RefreshToken.for_user(user)

    def validate(self, attrs):
        data = super().validate(attrs)

        refresh = self.get_token(self.user)

        data["refresh"] = str(refresh)
        data["access"] = str(refresh.access_token)

        return data

还有

# views.py
from rest_framework_simplejwt.views import TokenObtainPairView

class EmailTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer

当然

#urls.py
from rest_framework_simplejwt.views import TokenRefreshView
from .views import EmailTokenObtainPairView

url("token/", EmailTokenObtainPairView.as_view(), name="token_obtain_pair"),
url("refresh/", TokenRefreshView.as_view(), name="token_refresh"),
q0qdq0h2

q0qdq0h23#

这个问题已经有一段时间了,但是,我为@Mic的答案加了+1。顺便说一句,仅仅如下更新到TokenObtainPairSerializer还不够吗?

from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.serializers import (
    TokenObtainPairSerializer, User
)

class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
    username_field = User.EMAIL_FIELD

class EmailTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer
von4xj4u

von4xj4u4#

让我们总结一下上述解决方案:
1-使用Django命令创建两个应用程序,一个用于新令牌,另一个用于用户:

python manage.py startapp m_token  # modified token
python manage.py startapp m_user  # modified user

2-在m_token中,创建serializers.py并覆盖序列化程序以将username替换为email字段:

# serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer, User

class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
        username_field = User.EMAIL_FIELD

3-在m_token中,覆盖视图以使用新的序列化程序替换序列化程序:

# views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializer import CustomTokenObtainPairSerializer

class EmailTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer

4-在m_token中,创建urls.py并给予路径,如下所示:

# urls.py
from django.urls import path
from .views import TokenObtainPairView
from rest_framework_simplejwt.views import TokenRefreshView, TokenVerifyView

urlpatterns = [
    path(r'token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path(r'token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path(r'token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]

5-在m_user中,覆盖用户模型,如下所示:

# models.py
from django.contrib.auth.models import AbstractUser

class MUser(AbstractUser):
    USERNAME_FIELD = 'email'
    EMAIL_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

6-在django项目根目录中,将AUTH_USER_MODEL = 'm_user.MUser'添加到setting.py中。
我在我的项目中测试了它,它工作得很完美。我希望我没有错过任何东西。这样,swagger也会在令牌参数中显示“email”而不是“username”:

l3zydbqr

l3zydbqr5#

除了@Mic的答案外,记住设置USERNAME_FIELD = 'email',在用户模型中可能是REQUIRED_FIELDS = ['username']

ssgvzors

ssgvzors6#

对于那些使用自定义用户模型的用户,您只需添加以下行:

class User(AbstractUser):
    ... 
    email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

然后,在urls.py:

from rest_framework_simplejwt.views import TokenObtainPairView

urlpatterns = [
    ... 
    path('api/login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),

相关问题