首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使用"django-rest-framework-simplejwt“中的”`email`“而不是”username“来生成令牌?

如何使用"django-rest-framework-simplejwt“中的”`email`“而不是”username“来生成令牌?
EN

Stack Overflow用户
提问于 2019-01-12 05:35:16
回答 6查看 6.7K关注 0票数 13

在django中-rest框架-simplejwt插件默认使用usernamepassword。但是我想使用email而不是username。所以,我确实喜欢以下几点:

序列化程序中的

代码语言:javascript
复制
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

视图:

代码语言:javascript
复制
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

而且很管用!!

现在我的问题是,我怎样才能更有效地做到这一点?有人能对此提出建议吗?提前谢谢。

EN

回答 6

Stack Overflow用户

发布于 2019-08-19 11:52:33

这个答案是给未来读者的,因此包含了额外的信息。

为了简化身份验证后端,您需要连接多个类。我建议在下面执行选项1 (还有可选的选项3,您的简化版本)。在你读到之前,有几个笔记:

  • 备注1: django不强制执行所需的电子邮件,或者在用户创建时是唯一的(您可以覆盖这一点,但它是非主题的)!因此,选项3(您的实现)可能会给您带来重复电子邮件的问题。
  • 备注1b:使用User.objects.filter(email__iexact=...)以不区分大小写的方式匹配电子邮件。
  • 备注1c:使用get_user_model()来代替默认的用户模型,这确实是初学者的生命保护程序!
  • 备注2:避免将用户打印到控制台。您可能正在打印敏感数据。

至于三个备选方案:

  1. 使用f.e调整django身份验证后端。class EmailModelBackend(ModelBackend)和替换身份验证函数。
    • 不调整令牌索赔
    • 不依赖于JWT类/中间件(SimpleJWT、JWT或其他)
    • 还调整其他身份验证类型(会话/Cookie/non等)
    • 所需的输入参数仍然是用户名,如下所示。如果你不喜欢就调整一下,但是要小心。(可能会破坏您的导入/插件,而不是必需的!)

  1. django.contrib.auth 替换django authenticate(username=, password=, **kwarg)
    • 不调整令牌索赔
    • 您还需要替换令牌后端,因为它应该使用不同的身份验证,就像上面所做的那样。
    • 不使用authenticate(...)调整其他应用程序,只替换JWT (如果设置为这样的话)参数是不需要的,因此这个选项不太建议使用)。

  1. 实现MyTokenObtainPairSerializer与电子邮件作为索赔。
    • 现在电子邮件被发送回JWT数据(而不是id)。
    • 与选项1一起,您的应用程序身份验证已经成为用户名不可知论。

选项1 (请注意,这也允许用户名!):

代码语言:javascript
复制
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:您可以指定标识声明和在settings.py (或设置文件)中添加的数据来使用电子邮件。这将使您的前端应用程序也使用电子邮件进行索赔(而不是默认的用户.id)。

代码语言:javascript
复制
SIMPLE_JWT = {
    'USER_ID_FIELD': 'id', # model property to attempt claims for
    'USER_ID_CLAIM': 'user_id', # actual keyword in token data
}

但是,请注意创建者发出的唯一性警告:

例如,指定“用户名”或“电子邮件”字段将是一个糟糕的选择,因为帐户的用户名或电子邮件可能会根据给定服务中帐户管理的设计方式而改变。

如果你能保证唯一性,你就都准备好了。

票数 8
EN

Stack Overflow用户

发布于 2019-11-13 15:08:40

为什么你要复制和粘贴那么多而不是子类呢?我要处理的是:

代码语言:javascript
复制
# 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

代码语言:javascript
复制
# views.py
from rest_framework_simplejwt.views import TokenObtainPairView

class EmailTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer

当然还有

代码语言:javascript
复制
#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"),
票数 3
EN

Stack Overflow用户

发布于 2020-09-16 16:39:42

这个问题已经有一段时间了,但是,我添加了+1作为@Mic的答案。顺便问一下,仅按以下方式更新到TokenObtainPairSerializer不就足够了吗?

代码语言:javascript
复制
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
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54157056

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档