Django-使用电子邮件登录


84

我希望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)

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

Answers:


103

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

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

然后,在设置中将该后端设置为auth后端:

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

已更新。继承自ModelBackendget_user()已经实现的方法。

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


6
使用Django 1.9.8时出现错误:“ EmailBackend”对象没有属性“ get_user”。通过根据此stackoverflow.com/a/13954358/2647009
baltasvejas

请指定此代码可用于哪个Django版本。有些人抱怨缺少get_user方法。
尤尼斯·亨尼(Younes Henni)博士

2
if user.check_password(password):您可能不只是想要通过以下方式包含Django默认情况下的功能ModelBackendif user.check_password(password) and self.user_can_authenticate(user):检查用户是否拥有is_active=True
jmq

1
这是否不容易受到计时攻击,因为它不包含源代码中的Django缓解措施?
加布里埃尔·加西亚

现在,请求的正文应包含诸如用户名和密码之类的字段。有什么办法可以将其更改为电子邮件和密码?
卡罗尔

55

如果您要开始一个新项目,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)停止工作。


3
运行createsuperuser时,这本身会引发错误:TypeError:create_superuser()缺少1个必需的位置参数:'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
Michal Holub

17
完整的说明这里:fomfus.com/articles/...
user2061057

1
同样,如果您要创建可重用的应用程序,则Django文档建议不要使用自定义用户模型。
djvg

11

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',)

10

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

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

@zerohedge向身份验证方法的参数添加请求。见docs.djangoproject.com/en/2.2/topics/auth/customizing/...

它也让你打开一个定时攻击,这是值得尝试模仿密切Django的执行,以防止这些漏洞:github.com/django/django/blob/master/django/contrib/auth/...
加夫列尔·加西亚·

这还将使不活动的用户进行身份验证。
加布里埃尔·加西亚

5

Django 2.x

如上面Ganesh在django 2.x中提到的那样,authenticate方法现在需要一个请求参数。

# 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)

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

# settings.py
AUTHENTICATION_BACKENDS = ['path.to.ModelBackend']

您的自定义用户模型将需要使活动和经过验证的用户的电子邮件具有唯一性,您可以使用以下方式简单地执行此操作:

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'

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


4

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

永远记得将其添加到您的settings.py正确的身份验证后端


1
我的理解是否正确,UserModel().set_password(password)是否可以通过执行大致相同数量的加密工作来防止攻击者确定用户是否存在(我假设这是您所指的定时攻击)?
大埔

1
@GrandPhuba您完全正确
Gabriel Garcia

2

您应该自定义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']

更新您的代码以requestauthenticatedjango 2.1.1
Ganesh

2

对于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']

1
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文件内的身份验证后端类


1

很简单 不需要任何其他类。

使用电子邮件创建和更新用户时,只需在电子邮件中设置用户名字段即可。

这样,当您对用户名字段进行身份验证时,它将与电子邮件的值相同。

编码:

# 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)

1
请添加一些示例代码以帮助演示您的答案如何帮助解决问题。
Suit Boy Apps

1
我的答案很简单,应该很明显。我不会打扰的 我不在乎Stackoverflow上的积分系统。我将其张贴在这里:User.objects.create_user(username = post_data ['email']等...)
Casman Ridder

1
@CasmanRidder如果您不添加其他信息,您的答案将被删除。
10 Rep

我必须更新我的帖子,否则您将删除我的帖子我必须更改...我必须我必须LOL
Casman Ridder


0

通过电子邮件进行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"WelcomeBack{user_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')

您可以添加一些文字来解释您的答案是什么以及它如何帮助回答问题吗?
雅克兹

编辑。谢谢
Shakil Ahmmed '19

0

如果您创建了自定义数据库,则从那里开始验证您的电子邮件ID和密码。

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

2.分配列表中获取 object_query_list

3.将列表转换为字符串

例如:

  1. 将HTMLEmail_idPasswordViews.py

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

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

  2. 从数据库中获取电子邮件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)

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

    Email_Value = Email[0]

    Password_Value=Password[0]

  4. 将列表转换为字符串

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

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

最后您的登录条件

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

0

我为此创建了一个帮助器:function 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)

0

似乎这样做的方法已在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

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

AUTHENTICATION_BACKENDS = (
    'appname.authentication.EmailBackend',
)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.