簡體   English   中英

如何在“django-rest-framework-simplejwt”中使用“email”而不是“username”來生成令牌?

[英]How can I use `email` in "django-rest-framework-simplejwt" instead of `username` to generate token?

在 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

它有效!

現在我的問題是,我怎樣才能更有效地做到這一點? 任何人都可以對此提出建議嗎? 提前致謝。

這個答案是為未來的讀者准備的,因此包含額外的信息。

為了簡化身份驗證后端,您需要掛鈎多個類。 我建議在下面執行選項 1 (以及可選的選項 3 ,您的簡化版本)。 在繼續閱讀之前,請注意以下幾點:

  • 注意 1: django 不會根據需要強制執行電子郵件或在用戶創建時是唯一的(您可以覆蓋它,但它是題外話)! 因此,選項 3(您的實施)可能會給您帶來重復電子郵件的問題。
  • 注 1b:使用User.objects.filter(email__iexact=...)以不區分大小寫的方式匹配電子郵件。
  • Note 1c:使用get_user_model()以防您將來替換默認用戶模型,這對於初學者來說真的是一個救命稻草!
  • 注意 2:避免將用戶打印到控制台。 您可能正在打印敏感數據。

至於3個選項:

  1. 使用fe class EmailModelBackend(ModelBackend)調整django身份驗證后端並替換身份驗證功能。
    • 不調整令牌聲明
    • 不依賴於 JWT 類/中間件(SimpleJWT、JWT 或其他)
    • 還調整了其他認證類型​​(Session/Cookie/non-API auth 等)
    • 所需的輸入參數仍然是username ,示例如下。 如果您不喜歡它,請進行調整,但要小心。 (可能會破壞您的導入/插件,這不是必需的!)
  2. django.contrib.auth替換 django authenticate(username=, password=, **kwarg)
    • 不調整令牌聲明
    • 您還需要替換令牌后端,因為它應該使用不同的身份驗證,就像您在上面所做的那樣。
    • 不使用authenticate(...)調整其他應用程序,只替換 JWT auth(如果您這樣設置)參數不是必需的,因此不建議使用此選項)。
  3. 使用電子郵件作為聲明實現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 ,您可以在TokenObtainPairView(serializer_class=MyTokenObtainPairSerializer).as_view()中使用TokenObtainPairView(serializer_class=MyTokenObtainPairSerializer).as_view() 覆蓋使用的令牌序列化器的小簡化。

注意 2:您也可以在 settings.py(或設置文件)中指定標識聲明和添加的數據以使用電子郵件。 這將使您的前端應用程序也使用電子郵件進行聲明(而不是默認用戶.id)

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

但是,請注意創作者給出的獨特性警告:

例如,指定“用戶名”或“電子郵件”字段將是一個糟糕的選擇,因為帳戶的用戶名或電子郵件可能會根據給定服務中帳戶管理的設計方式而改變。

如果你能保證唯一性,你就萬事大吉了。

為什么你復制和粘貼這么多而不是子類化? 我讓它與:

# 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"),

這個問題已經有一段時間了,但是,我為@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

讓我們總結一下上面的解決方案:

1- 通過 Django 命令創建兩個應用程序。 一個用於新令牌,另一個用於用戶:

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

2- 在 m_token 中,創建 serializers.py 並覆蓋序列化程序以用電子郵件字段替換用戶名:

# 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 還在令牌參數中顯示“電子郵件”而不是“用戶名”: 在此處輸入圖片說明

除了@Mic 的回答,記得在 User 模型中設置USERNAME_FIELD = 'email'並且可能是REQUIRED_FIELDS = ['username']

對於使用自定義 User 模型的用戶,您只需添加這些行:

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'),

在此答案的第 2 步中,需要一個小修復才能使其正常工作: User is not part of simple jwt serializers。 您應該像這樣使用get_user_model

# serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from django.contrib.auth import get_user_model

class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):    
    username_field = get_user_model().EMAIL_FIELD

然后,即使您使用自定義用戶模型,它也能正常工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM