Django - 使用电子邮件登录

Posted

技术标签:

【中文标题】Django - 使用电子邮件登录【英文标题】:Django - Login with Email 【发布时间】:2016-09-16 20:42:57 【问题描述】:

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

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

我怎样才能改变让 Django 使用电子邮件进行身份验证?

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

username = `abcd28`
user_email = `abcd@gmail.com`
user = User.objects.create_user(username, user_email, user_pass)

这就是我的登录方式。

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)

除了先获取用户名,还有其他登录方式吗?

【问题讨论】:

【参考方案1】:

您应该编写一个自定义身份验证后端。这样的事情会起作用:

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

class EmailBackend(ModelBackend):
    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

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

AUTHENTICATION_BACKENDS = ['path.to.auth.module.EmailBackend']

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

在此处查看文档:https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#writing-an-authentication-backend

【讨论】:

使用 django 1.9.8 我有一个错误:'EmailBackend' 对象没有属性'get_user'。根据这个***.com/a/13954358/2647009添加'get_user'方法解决@ 请指定此代码适用于哪个 Django 版本。有些人抱怨缺少 get_user 方法。 而不仅仅是if user.check_password(password):,您可能希望通过ModelBackend 包含Django 默认执行的操作:if user.check_password(password) and self.user_can_authenticate(user): 以检查用户是否拥有is_active=True 这是否容易受到定时攻击,因为它不包括源代码上的 Django 缓解措施? 现在,请求正文应包含用户名和密码等字段。有没有办法改成邮箱和密码?【参考方案2】:

如果您要开始一个新项目,django 强烈建议您设置自定义用户模型。 (见https://docs.djangoproject.com/en/dev/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

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

【讨论】:

运行 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() return user 这里的完整说明:fomfus.com/articles/… 如果您正在创建一个可重用应用程序,那么 Django 文档advise against 使用自定义用户模型。 这个解是不是很长【参考方案3】:

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

默认情况下,AUTHENTICATION_BACKENDS 设置为:

['django.contrib.auth.backends.ModelBackend']

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

AUTHENTICATION_BACKENDS = ('appname.filename.EmailBackend',)

【讨论】:

这太好了,谢谢。我已经差不多完成了一个项目、模板、表单、视图等等,所以重新开始并没有那么吸引人!现在我可以对电子邮件地址进行身份验证有没有办法删除用户名字段,使其不包含在身份验证和模板中呈现的表单中? 为什么在模型中使用这个而不是USERNAME_FIELD 变量? 这样您就可以使用电子邮件和用户名登录 谢谢伙计。你的解释很好【参考方案4】:

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

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

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

【讨论】:

这对我有用,直到我从 1.11 升级到 2.1.5。知道为什么它不适用于此版本吗? @zerohedge 将请求添加到验证方法的参数。见docs.djangoproject.com/en/2.2/topics/auth/customizing/… 它也让你容易受到定时攻击,值得尝试密切模仿 Django 实现以防止此类漏洞:github.com/django/django/blob/master/django/contrib/auth/… 这也将使非活动用户能够进行身份验证。【参考方案5】:

Django 4.0

您可以通过两种主要方式实现电子邮件身份验证,请注意以下几点:

电子邮件在用户模型上不应是唯一的,以减少拼写错误和恶意使用。 只有经过验证的电子邮件才可用于身份验证(如我们已发送验证电子邮件并且他们已单击验证链接)。 我们应该已验证电子邮件地址发送电子邮件。

自定义用户模型

当开始一个新项目时,自定义用户模型是recommended,因为更改中间项目可能会很棘手。

我们将添加一个email_verified 字段,以将电子邮件身份验证限制为具有经过验证的电子邮件地址的用户。

# app.models.py
from django.db import models
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    email_verified = models.BooleanField(default=False)

然后,我们将创建一个自定义身份验证后端,用给定的电子邮件地址替换用户名。

此后端将与显式设置email 字段以及设置username 字段的身份验证表单一起使用。

# app.backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q

UserModel = get_user_model()


class CustomUserModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD, kwargs.get(UserModel.EMAIL_FIELD))
        if username is None or password is None:
            return
        try:
            user = UserModel._default_manager.get(
                Q(username__exact=username) | (Q(email__iexact=username) & Q(email_verified=True))
            )
        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

然后我们修改我们的项目settings.py 以使用我们的自定义用户模型和身份验证后端。

# project.settings.py

AUTH_USER_MODEL = "app.User"
AUTHENTICATION_BACKENDS = ["app.backends.CustomUserModelBackend"]

确保在运行migrate 之前运行manage.py makemigrations,并且第一次迁移包含这些设置。

扩展用户模型

虽然性能不如自定义User 模型(需要辅助查询),但在现有项目中使用extend 现有User 模型可能会更好,并且根据登录流程和验证过程可能更适合。

我们通过AUTH_USER_MODEL 设置创建从EmailVerification 到我们项目使用的User 模型的一对一关系。

# app.models.py
from django.conf import settings
from django.db import models


class EmailVerification(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_query_name="verification"
    )
    verified = models.BooleanField(default=False)

我们还可以创建一个自定义管理员,其中包含我们的内联扩展。

# app.admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

from .models import EmailVerification

UserModel = get_user_model()


class VerificationInline(admin.StackedInline):
    model = EmailVerification
    can_delete = False
    verbose_name_plural = 'verification'


class UserAdmin(BaseUserAdmin):
    inlines = (VerificationInline,)


admin.site.unregister(UserModel)
admin.site.register(UserModel, UserAdmin)

然后,我们创建一个类似于上面的后端,只检查相关模型verified 字段。

# app.backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q

UserModel = get_user_model()


class ExtendedUserModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD, kwargs.get(UserModel.EMAIL_FIELD))
        if username is None or password is None:
            return
        try:
            user = UserModel._default_manager.get(
                Q(username__exact=username) | (Q(email__iexact=username) & Q(verification__verified=True))
            )
        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

然后我们修改我们的项目settings.py 以使用我们的身份验证后端。

# project.settings.py

AUTHENTICATION_BACKENDS = ["app.backends.ExtendedUserModelBackend"]

然后您可以makemigrationsmigrate 为现有项目添加功能。

注意事项

如果用户名不区分大小写,请将 Q(username__exact=username) 更改为 Q(username__iexact=username)。 在生产中防止新用户使用现有的已验证电子邮件地址进行注册。

【讨论】:

【参考方案6】:

Django 2.X 的电子邮件和用户名身份验证

考虑到这是一个常见问题,这里有一个模仿 Django source code 的自定义实现,但它使用用户名或电子邮件对用户进行身份验证,不区分大小写,保留 timing attack protection 和 not authenticating inactive users。

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

永远记得将它添加到您的设置中。py 正确的Authentication Backend。

【讨论】:

我的理解是正确的,UserModel().set_password(password) 是为了防止攻击者通过执行大致相同数量的加密工作来确定用户是否存在(我认为这是时机攻击你的意思)? @GrandPhuba 你是 100% 正确的【参考方案7】:

看来这样做的方法已经用 Django 3.0 更新了。

我的一种工作方法是:

authentication.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

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

AUTHENTICATION_BACKENDS = (
    'appname.authentication.EmailBackend',
)

【讨论】:

【参考方案8】:

您应该自定义 ModelBackend 类。 我的简单代码:

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

class YourBackend(ModelBackend):

  def authenticate(self, username=None, password=None, **kwargs):
    UserModel = get_user_model()
    if username is None:
        username = kwargs.get(UserModel.USERNAME_FIELD)
    try:
        if '@' in username:
            UserModel.USERNAME_FIELD = 'email'
        else:
            UserModel.USERNAME_FIELD = 'username'

        user = UserModel._default_manager.get_by_natural_key(username)
    except UserModel.DoesNotExist:
        UserModel().set_password(password)
    else:
        if user.check_password(password) and self.user_can_authenticate(user):
            return user

并在 settings.py 文件中,添加:

AUTHENTICATION_BACKENDS = ['path.to.class.YourBackend']

【讨论】:

更新您的代码以在 django 2.1.1 的 authenticate 方法中包含 request 参数【参考方案9】:

使用 Django 2.x 的电子邮件和用户名进行身份验证

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

在settings.py中,添加以下行,

AUTHENTICATION_BACKENDS = ['appname.filename.EmailorUsernameModelBackend']

【讨论】:

【参考方案10】:

我为此创建了一个助手:函数authenticate_user(email, password)

from django.contrib.auth.models import User


def authenticate_user(email, password):
    try:
        user = User.objects.get(email=email)
    except User.DoesNotExist:
        return None
    else:
        if user.check_password(password):
            return user

    return None

class LoginView(View):
    template_name = 'myapp/login.html'

    def get(self, request):
        return render(request, self.template_name)

    def post(self, request):
        email = request.POST['email']
        password = request.POST['password']
        user = authenticate_user(email, password)
        context = 

        if user is not None:
            if user.is_active:
                login(request, user)

                return redirect(self.request.GET.get('next', '/'))
            else:
                context['error_message'] = "user is not active"
        else:
            context['error_message'] = "email or password not correct"

        return render(request, self.template_name, context)

【讨论】:

简单直接的解决方案!除非您在同一电子邮件下有多个用户,否则它将起作用。这是一个非常好的实现【参考方案11】:
from django.contrib.auth.models import User

from django.db import Q

class EmailAuthenticate(object):

    def authenticate(self, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(Q(email=username) | Q(username=username))
        except User.DoesNotExist:
            return None
        except MultipleObjectsReturned:
            return User.objects.filter(email=username).order_by('id').first()

        if user.check_password(password):
            return user
        return None

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

然后在settings.py:

AUTHENTICATION_BACKENDS = (
  'articles.backends.EmailAuthenticate',
)

articles 是我的 django 应用程序,backends.py 是我的应用程序中的 python 文件,EmailAuthenticate 是我的backends.py 文件中的身份验证后端类

【讨论】:

【参考方案12】:

对于 Django 2

username = get_object_or_404(User, email=data["email"]).username
        user = authenticate(
            request, 
            username = username, 
            password = data["password"]
        )
        login(request, user)

【讨论】:

【参考方案13】:

使用 Django 2.x 的电子邮件进行身份验证

def admin_login(request):
if request.method == "POST":
    email = request.POST.get('email', None)
    password = request.POST.get('password', None)
    try:
        get_user_name = CustomUser.objects.get(email=email)
        user_logged_in =authenticate(username=get_user_name,password=password)
        if user_logged_in is not None:
            login(request, user_logged_in)
            messages.success(request, f"WelcomeBackuser_logged_in.username")
            return HttpResponseRedirect(reverse('backend'))
        else:
            messages.error(request, 'Invalid Credentials')
            return HttpResponseRedirect(reverse('admin_login'))
    except:
        messages.warning(request, 'Wrong Email')
        return HttpResponseRedirect(reverse('admin_login'))

else:
    if request.user.is_authenticated:
        return HttpResponseRedirect(reverse('backend'))
    return render(request, 'login_panel/login.html')

【讨论】:

您能否添加一些文字来解释您的答案的作用以及它如何帮助回答问题? 已编辑。谢谢【参考方案14】:

如果您创建了自定义数据库,如果您想验证您的电子邮件 ID 和密码。

    使用models.objects.value_list('db_columnname').filter(db_emailname=textbox email)获取电子邮件ID和密码

2.assign in list fetched object_query_list

3.将列表转换为字符串

例如:

    Views.py中取Html Email_idPassword

    u_email = request.POST.get('uemail')

    u_pass = request.POST.get('upass')

    从数据库中获取电子邮件 ID 和密码

    Email = B_Reg.objects.values_list('B_Email',flat=True).filter(B_Email=u_email)

    Password = B_Reg.objects.values_list('Password',flat=True).filter(B_Email=u_email)

    Query 值集中获取列表中的电子邮件 ID 和密码值

    Email_Value = Email[0]

    Password_Value=Password[0]

    将列表转换为字符串

    string_email = ''.join(map(str, Email_Value))

    string_password = ''.join(map(str, Password_Value))

最后是你的登录条件

if (string_email==u_email and string_password ==u_pass)

【讨论】:

【参考方案15】:

很简单。不需要任何额外的类。

当您使用电子邮件创建和更新用户时,只需使用电子邮件设置用户名字段。

这样,当您验证用户名字段将与电子邮件的值相同。

代码:

# Create
User.objects.create_user(username=post_data['email'] etc...)

# Update
user.username = post_data['email']
user.save()

# When you authenticate
user = authenticate(username=post_data['email'], password=password)

【讨论】:

请添加一些示例代码来帮助演示您的答案如何帮助解决问题。 @CasmanRidder 如果您不添加其他信息,您的答案将被删除。【参考方案16】:

默认用户模型继承/扩展一个抽象类。框架应该对一定数量的更改或变更宽松。

更简单的 hack 是执行以下操作: 这是在虚拟环境中

    转到您的 django 安装位置并找到 Lib 文件夹 导航到 django/contrib/auth/ 找到并打开models.py文件。找到 AbstractUser 类第 315 行

第 336 行在 email 属性上添加 unique 并将其设置为 true

email = models.EmailField(_('email address'), blank=True,unique=True)

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
    完成,进行迁移和迁移

风险自负,

【讨论】:

以上是关于Django - 使用电子邮件登录的主要内容,如果未能解决你的问题,请参考以下文章

python Django:使用用户名或电子邮件登录

Django allauth 社交登录:使用注册的电子邮件自动链接社交网站配置文件

在 django 2.2.5 中使用用户名和密码登录

Django:创建电子邮件登录字段而不是用户名后,超级用户和用户无法登录到管理员和用户页面

如何结合突变在 django-graphql-jwt 中创建或登录用户?

使用Django实现发邮件功能