在 django-rest-framework-simplejwt 插件中,默认使用
username
和 password
。但我想用 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,您的简化版本)。在继续阅读之前,请注意以下几点:
User.objects.filter(email__iexact=...)
以不区分大小写的方式匹配电子邮件。get_user_model()
以防将来更换默认用户模型,这对初学者来说真的是救星!至于3个选项:
class EmailModelBackend(ModelBackend)
并替换验证功能。
django.contrib.auth替换 django
authenticate(username=, password=, **kwarg)
authenticate(...)
调整其他应用程序,仅替换 JWT 身份验证(如果您这样设置)
不需要参数,因此不建议使用此选项)。MyTokenObtainPairSerializer
作为索赔。
选项 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()
。小的简化覆盖了使用的 token 序列化器。
注2: 您也可以在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
}
但是,请注意创作者给出的独特性警告:
例如,指定“用户名”或“电子邮件”字段将是一个糟糕的选择,因为帐户的用户名或电子邮件可能会根据给定服务中帐户管理的设计方式而变化。
如果你能保证唯一性,那就万事大吉了。
为什么要复制和粘贴这么多而不是子类化?我可以使用它:
# 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"),
对于那些使用自定义用户模型的人,您只需添加这些行:
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'),
这个问题已经有一段时间了,但是,我为@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 中,创建序列化器.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的答案之外,记得设置
USERNAME_FIELD = 'email'
并且可能是用户模型中的REQUIRED_FIELDS = ['username']
。
使用此代码,您可以允许用户使用用户名字段中的用户名或电子邮件登录。您可以添加一些行来验证电子邮件。
class TokenPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
raw_username = attrs["username"]
users = User.objects.filter(email=raw_username)
if(users.exists()):
attrs['username'] = users.first().username
# else:
# raise serializers.ValidationError("Only email is allowed!")
data = super(TokenPairSerializer, self).validate(attrs)
return data