当使用自定义用户模型,扩展 AbstractBaseUser
时,当USERNAME_FIELD指向可为空的字段时,我遇到了问题
我想要完成的是允许多种登录方法(Facebook,用户名),为此我有两个唯一的标识符字段(facebook_id,用户名),但两者都必须可以为空,因为一个帐户不必同时具有这两个(并且大多数帐户不会兼得
因此,对于使用用户名登录,我依赖于Django内置的登录身份验证后端
它崩溃的地方是尝试使用其他登录方法(facebook)登录时,首先尝试使用Django auth
class ModelBackend(object):
"""
Authenticates against settings.AUTH_USER_MODEL.
"""
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)
if username is None
hack用于修复用户名字段实际上未被调用"username"的情况,因此 username
参数为空但实际提供的用户名可以在 kwargs
中找到
问题是它最终以无值结束(因为kwargs包含facebook_id = 12345)并最终将其传递给 get_by_natural_key
def get_by_natural_key(self, username):
return self.get(**{self.model.USERNAME_FIELD: username})
大致翻译为 User.objects.get(username=None)
由于我的用户名字段是可空的,这会返回多个结果,整个事件抛出一个models.MultipleObjectsReturned异常(“get()返回多个用户 - 它返回417!”)并阻止任何其他auth后端运行
从我的角度来看,有几个问题:
-
ModelBackend不应该只是盲目地试图检索用户,如果它试图找出提供的用户名结果
None
-
get_by_natural_key应该捕获MultipleObjectsReturned并正常失败,或者authenticate()应该处理该场景并且无法记录用户,而不是轰炸整个auth机制
已知的解决方法:
我知道我可以将ModelBackend移动到最后一个尝试的方法,但这有时会使得哑查询运行而不是总是,我宁愿修复它,所以它永远不会 . 我也稍微关心它的性能,因为用户名=无用户增长
我也知道我可以扩展ModelBackend,修补漏洞并且不使用库存ModelBackend,但我不知道是否有任何问题导致这种方法...使用ModelBackend的现有会话丢失了吗?
出于简化目的,我跳过了实际使用可空的电子邮件字段作为用户名的部分:D我知道为此我可以轻松使用自定义身份验证后端但是一旦我获得了nullable-email-field-login工作,我开始以为我可以通过使用户名字段也可以为空来为我来自Facebook的用户生成用户名(以及让他们稍后在这个过程中选择一个未使用的用户名)让我自己有些头痛:D
我担心有一个可以为空的USERNAME_FIELD将使Django以各种不同的方式失败,我没有看到任何警告或解释原因 . 愚蠢的是,支持多种登录方案的系统无法在其库存认证标识符为空的情况下存活 .
1 回答
由于您使用自己的方法替换默认的
User
模型,因此在使用None
自然键调用get_by_natural_key
时会提升DoesNotExist
,因为这种情况清楚地表明您的用户正在使用其他方案进行身份验证 . 您的模型没有记录可以具有合法None
自然键的情况,因此无论如何它应该为这样的键引发DoesNotExist
.