django - 如何在“django-rest-framework-simplejwt”中使用“电子邮件”而不是“用户名”来生成令牌?
问题描述
在 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,您的简化版本)。在您继续阅读之前,请注意几点:
- 注意 1: django 不会根据需要强制使用电子邮件或在用户创建时是唯一的(您可以覆盖它,但它是题外话)!因此,选项 3(您的实施)可能会给您带来重复电子邮件的问题。
- 注 1b:用于
User.objects.filter(email__iexact=...)
以不区分大小写的方式匹配电子邮件。 - 注1c:
get_user_model()
万一以后更换默认用户模型时使用,这对初学者来说真的是救命稻草! - 注意 2:避免将用户打印到控制台。您可能正在打印敏感数据。
至于3个选项:
- 使用 fe 调整 django 身份验证后端
class EmailModelBackend(ModelBackend)
并替换身份验证功能。- 不调整令牌声明
- 不依赖于 JWT 类/中间件(SimpleJWT、JWT 或其他)
- 还调整了其他认证类型(Session/Cookie/non-API auth等)
- 所需的输入参数仍然是username,示例如下。如果您不喜欢它,请调整,但要小心。(可能会破坏您的导入/插件,并且不是必需的!)
authenticate(username=, password=, **kwarg)
从django.contrib.auth替换 django- 不调整令牌声明
- 您还需要替换令牌后端,因为它应该使用不同的身份验证,就像您在上面所做的那样。
- 不使用 调整其他应用程序
authenticate(...)
,仅替换 JWT 身份验证(如果您这样设置)参数不是必需的,因此不建议使用此选项)。
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()
在你的 urls.py中使用。覆盖使用的令牌序列化程序的小简化。
注意 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
}
但是,请注意创作者给出的独特性警告:
例如,指定“用户名”或“电子邮件”字段将是一个糟糕的选择,因为帐户的用户名或电子邮件可能会根据给定服务中帐户管理的设计方式而改变。
如果你能保证唯一性,你就万事大吉了。
推荐阅读
- oracle-apex - 将顶点日历与谷歌日历集成
- google-pay - google订阅升级后收不到linkedPurchaseToken
- javascript - 在使用 canvas api 在浏览器中将 svg 转换为 png 时,svg 中的嵌入图像在 Safari 中随机为空白
- mysql - 我们可以在游标循环内的更新查询中使用内置函数吗?
- wordpress - 为什么一次执行多个查询会导致 Wordpress 出错,而 phpMyAdmin 不会?
- javascript - Promise 在 NodeJS 的 Cron 作业中不起作用
- flutter - 循环内部是异步的,外部怎么同步
- python - 比较 Django 中 2 个不同模型对象的 views.py 中的对象
- web-crawler - 我们是否应该在 robots txt 文件中提及孤立页面以禁止抓取?
- apache-iotdb - Apache IoTDB:xxx(列名)得到的值为NULL