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)

Чи існує якийсь інший варіант входу, крім отримання спочатку імені користувача?

Відповіді:


104

Вам слід написати власний сервіс автентифікації. Щось на зразок цього буде працювати:

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


6
Використовуючи django 1.9.8, у мене сталася помилка: об'єкт "EmailBackend" не має атрибута "get_user". Вирішена шляхом додавання методу «get_user» в Відповідно до цього stackoverflow.com/a/13954358/2647009
baltasvejas

Вкажіть, для якої версії Django цей код може працювати. Деякі скаржаться на відсутність методу get_user.
Доктор Юнес Хенні

2
Замість того, щоб просто if user.check_password(password):напевно хотіти включити те, що Django робить за замовчуванням за допомогою ModelBackend: if 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
Під час запуску createuperuser це саме видає помилку: 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

У мене була подібна вимога, коли будь-яке ім'я користувача / електронна адреса повинна працювати для поля імені користувача. Якщо хтось шукає спосіб аутентифікації для цього, перевірте наступний робочий код.

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

Також додайте AUTHENTICATION_BACKENDS = ('path.to.CustomBackend',) у settings.py


Це працювало для мене, поки я не перейшов з 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/…
Габріель Гарсія,

Це також дозволить неактивним користувачам пройти автентифікацію.
Габріель Гарсія,

11

Аутентифікація електронної пошти для Django 3.x

Для використання електронної пошти / імені користувача та пароля для автентифікації замість автентифікації імені користувача та пароля за замовчуванням нам потрібно замінити два методи класу ModelBackend: authenticate () та get_user ():

Метод get_user бере user_id - який може бути іменем користувача, ідентифікатором бази даних або іншим, але повинен бути унікальним для вашого об'єкта користувача - і повертає об'єкт користувача або None. Якщо ви не зберігали електронну пошту як унікальний ключ, вам доведеться подбати про кілька результатів, повернутих для набору запитів. У наведеному нижче коді це вирішено шляхом повернення першого користувача зі списку, що повернувся.

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

5

Django 2.x

Як згадувалося Ганешем вище для 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)

додайте свій сервер до налаштувань проекту

# 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

Завжди пам’ятайте про те, щоб додати до нього ваші налаштування . Скопіюйте правильний інтерфейс аутентифікації .


1
Чи правильно я розумію, що UserModel().set_password(password)це існує, щоб запобігти зловмисникам визначити, чи існує користувач чи ні, виконуючи приблизно однаковий обсяг криптографічної роботи, незалежно від того (я припускаю, це саме та атака часу, яку ви мали на увазі)?
Grand Phuba

1
@GrandPhuba Ви цілком праві
Габріель Гарсія

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

Оновіть свій код, щоб включити requestпараметр у authenticateметод для django 2.1.1
Ганеш

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

де article - це мій 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 (ім'я користувача = post_data ['електронна пошта'] тощо ...)
Casman Ridder

1
@CasmanRidder Ваша відповідь буде видалена, якщо ви не додасте додаткову інформацію.
10 Rep

omg я повинен оновити мій пост, інакше ти
видалиш


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

Чи можете ви додати трохи тексту, щоб пояснити, що робить ваша відповідь і як це допомагає відповісти на питання?
Jaquez

Відредаговано. Дякую
Шакіл Ахмед

0

Якщо ви створили власну базу даних, звідти ви хочете перевірити свій ідентифікатор електронної пошти та пароль.

  1. Завантажте ідентифікатор електронної пошти та пароль за допомогою models.objects.value_list('db_columnname').filter(db_emailname=textbox email)

2. присвоїти у списку отриманих object_query_list

3. Перетворити список на рядок

Приклад:

  1. Візьміть HTML Email_idі Passwordзначення вViews.py

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

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

  2. Заберіть ідентифікатор електронної пошти та пароль із бази даних

    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набору значень

    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

Я створив помічник для цієї функції 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',
)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.