Django - 用电子邮件登录

Pyt*_*ast 53 django

我希望django通过电子邮件验证用户,而不是通过用户名.一种方法是提供电子邮件值作为用户名值,但我不希望这样.原因是,我有一个网址/profile/<username>/,因此我没有网址/profile/abcd@gmail.com/.

另一个原因是所有电子邮件都是唯一的,但有时会发生用户名.因此我将自动创建用户名为fullName_ID.

我怎样才能让Django通过电子邮件进行身份验证?

这就是我创建用户的方式.

username = `abcd28`
user_email = `abcd@gmail.com`
user = User.objects.create_user(username, user_email, user_pass)
Run Code Online (Sandbox Code Playgroud)

这是我登录的方式.

email = request.POST['email']
password = request.POST['password']
username = User.objects.get(email=email.lower()).username
user = authenticate(username=username, password=password)
login(request, user)
Run Code Online (Sandbox Code Playgroud)

除了首先获取用户名之外,还有其他任何登录信息吗?

mip*_*adi 75

您应该编写自定义身份验证后端.这样的东西会起作用:

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend

class EmailBackend(ModelBackend):
    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        try:
            user = UserModel.objects.get(email=username)
        except UserModel.DoesNotExist:
            return None
        else:
            if user.check_password(password):
                return user
        return None
Run Code Online (Sandbox Code Playgroud)

然后,在您的设置中将该后端设置为您的身份验证后端:

AUTHENTICATION_BACKENDS = ['path.to.auth.module.EmailBackend']
Run Code Online (Sandbox Code Playgroud)

更新.继承,ModelBackend因为它实现了get_user()已经的方法.

  • 使用django 1.9.8我有一个错误:'EmailBackend'对象没有属性'get_user'.根据此http://stackoverflow.com/a/13954358/2647009添加'get_user'方法解决 (6认同)
  • @AzharUddinSheikh 根据我的回答,它很容易受到定时攻击;如果找不到用户,它不会检查不运行哈希算法的密码,这将导致更快的响应,因此,如果攻击者获得比平常更长的响应,则意味着用户存在(因为它必须检查涉及哈希函数的密码)。我的回答是 Django 在其代码上实现的方式(根据我的回答),它将始终对密码进行哈希处理,从而混淆任何计时信息。 (6认同)
  • 请指定此代码适用于哪个 Django 版本。有些人抱怨缺少 get_user 方法。 (2认同)
  • 您可能不只是`if user.check_password(password):`而是包括通过ModelBackend`默认情况下Django所做的工作:`if user.check_password(password)和self.user_can_authenticate(user):`以检查用户具有“ is_active = True”。 (2认同)
  • 这是否容易受到计时攻击,因为它不包含对源代码的 Django 缓解措施? (2认同)
  • 现在,请求正文应包含用户名和密码等字段。有什么办法可以将其更改为电子邮件和密码吗? (2认同)

小智 29

如果您正在开始一个新项目,django强烈建议您设置自定义用户模型.(参见https://docs.djangoproject.com/en/1.10/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project)

如果您这样做,请在您的用户模型中添加三行:

class MyUser(AbstractUser):
    USERNAME_FIELD = 'email'
    email = models.EmailField(_('email address'), unique=True) # changes email to unique and blank to false
    REQUIRED_FIELDS = [] # removes email from REQUIRED_FIELDS
Run Code Online (Sandbox Code Playgroud)

然后authenticate(email=email, password=password)工作,而authenticate(username=username, password=password)停止工作.

  • 完整说明:https://www.fomfus.com/articles/how-to-use-email-as-username-for-django-authentication-removing-the-username (11认同)
  • 运行 createsuperuser 时,这本身会引发错误:TypeError: create_superuser() missing 1 required positional argument: 'username'。您需要使用自定义用户管理器:`class MyUserManager(BaseUserManager): def create_superuser(self, email, password, **kwargs): user = self.model(email=email, is_staff=True, is_superuser=True, **kwargs ) user.set_password(password) user.save() 返回用户` (4认同)
  • 再说一次,Django 文档 [建议反对](https://docs.djangoproject.com/en/2.1/topics/auth/customizing/#reusable-apps-and-auth-user-model) 使用自定义用户模型,如果您正在创建一个*可重用*的应用程序。 (2认同)

Him*_*ngh 22

Django 3.x 的电子邮件身份验证

为了使用电子邮件/用户名和密码进行身份验证而不是默认的用户名和密码身份验证,我们需要覆盖 ModelBackend 类的两个方法:authenticate() 和 get_user():

get_user 方法接受一个 user_id——它可以是用户名、数据库 ID 或其他任何东西,但必须是你的用户对象唯一的——并返回一个用户对象或 None。如果您没有将电子邮件作为唯一键保存,则必须处理为 query_set 返回的多个结果。在下面的代码中,这是通过从返回的列表中返回第一个用户来处理的。

from django.contrib.auth.backends import ModelBackend, UserModel
from django.db.models import Q

class EmailBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try: #to allow authentication through phone number or any other field, modify the below statement
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
        except UserModel.DoesNotExist:
            UserModel().set_password(password)
        except MultipleObjectsReturned:
            return User.objects.filter(email=username).order_by('id').first()
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

    def get_user(self, user_id):
        try:
            user = UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None

        return user if self.user_can_authenticate(user) else None
Run Code Online (Sandbox Code Playgroud)

默认情况下,AUTHENTICATION_BACKENDS 设置为:

['django.contrib.auth.backends.ModelBackend']
Run Code Online (Sandbox Code Playgroud)

在 settings.py 文件中,在底部添加以下内容以覆盖默认值:

AUTHENTICATION_BACKENDS = ('appname.filename.EmailBackend',)
Run Code Online (Sandbox Code Playgroud)

  • 为什么在模型中使用此变量而不是“USERNAME_FIELD”变量? (2认同)

anu*_*b26 9

我有一个类似的要求,其中用户名/电子邮件应该用于用户名字段.如果有人正在寻找身份验证后端的方法,请查看以下工作代码.如果您只需要电子邮件,您可以更改查询集.

from django.contrib.auth import get_user_model  # gets the user_model django  default or your own custom
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q


# Class to permit the athentication using email or username
class CustomBackend(ModelBackend):  # requires to define two functions authenticate and get_user

    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()

        try:
            # below line gives query set,you can change the queryset as per your requirement
            user = UserModel.objects.filter(
                Q(username__iexact=username) |
                Q(email__iexact=username)
            ).distinct()

        except UserModel.DoesNotExist:
            return None

        if user.exists():
            ''' get the user object from the underlying query set,
            there will only be one object since username and email
            should be unique fields in your models.'''
            user_obj = user.first()
            if user_obj.check_password(password):
                return user_obj
            return None
        else:
            return None

    def get_user(self, user_id):
        UserModel = get_user_model()
        try:
            return UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
Run Code Online (Sandbox Code Playgroud)

还要在settings.py中添加AUTHENTICATION_BACKENDS =('path.to.CustomBackend',)

  • 这也将使非活动用户能够进行身份验证。 (2认同)

bob*_*bob 6

Django 2.x

正如 Ganesh 上面针对 django 2.x 所提到的,身份验证方法现在需要一个请求参数。

# backends.py
from django.contrib.auth import backends, get_user_model
from django.db.models import Q
UserModel = get_user_model()


class ModelBackend(backends.ModelBackend):

    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            # user = UserModel._default_manager.get_by_natural_key(username)
            # You can customise what the given username is checked against, here I compare to both username and email fields of the User model
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a nonexistent user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user
        return super().authenticate(request, username, password, **kwargs)
Run Code Online (Sandbox Code Playgroud)

将您的后端添加到您的项目设置中

# settings.py
AUTHENTICATION_BACKENDS = ['path.to.ModelBackend']
Run Code Online (Sandbox Code Playgroud)

您的自定义 User 模型需要为活跃和经过验证的用户创建独一无二的电子邮件,您可以简单地使用以下内容:

from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    objects = UserManager()
    email = models.EmailField(_('email address'), unique=True)

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        db_table = 'auth_user'
        swappable = 'AUTH_USER_MODEL'
Run Code Online (Sandbox Code Playgroud)

但是为了防止有人阻止其他人使用他们的电子邮件,您应该添加电子邮件验证,并让您的注册和登录过程考虑到电子邮件可能不是唯一的(并且可能阻止新用户使用现有的和经过验证的电子邮件地址)。

  • 我不明白你的评论,即电子邮件不应该是唯一的——拼写错误与什么有关系?电子邮件似乎应该是唯一的,否则最终可能会导致两个不同的用户输入同一封电子邮件。 (2认同)

Gab*_*cia 5

Django 2.X 的电子邮件和用户名认证

请记住,这是一个常见问题,这里有一个模仿Django 源代码的自定义实现,但它使用用户名或电子邮件对用户进行身份验证,不区分大小写,保持定时攻击 保护,不是对非活动用户进行身份验证

from django.contrib.auth.backends import ModelBackend, UserModel
from django.db.models import Q

class CustomBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
        except UserModel.DoesNotExist:
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

    def get_user(self, user_id):
        try:
            user = UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None

        return user if self.user_can_authenticate(user) else None
Run Code Online (Sandbox Code Playgroud)

永远记住将它添加到您的 settings.py 正确的Authentication Backend


小智 5

Django 3.0 似乎已经更新了这样做的方法。

我的工作方法是:

authentication.py # <-- 我将其放在应用程序中(在项目文件夹中与 settings.py 一起不起作用)

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User

class EmailBackend(BaseBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        try:
            user = UserModel.objects.get(email=username)
        except UserModel.DoesNotExist:
            return None
        else:
            if user.check_password(password):
                return user
        return None

    def get_user(self, user_id):
        UserModel = get_user_model()
        try:
            return UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
Run Code Online (Sandbox Code Playgroud)

然后将其添加到settings.py文件中

AUTHENTICATION_BACKENDS = (
    'appname.authentication.EmailBackend',
)
Run Code Online (Sandbox Code Playgroud)