Войдите в систему, используя либо адрес электронной почты, либо имя пользователя в Django

Я пытаюсь создать автономный сервер, чтобы позволить моим пользователям входить в систему, используя либо свой адрес электронной почты, либо свое имя пользователя в Django 1.6 с пользовательской моделью пользователя. Бэкэнд работает, когда я вхожу в систему с именем пользователя, но по какой-то причине не имеет адреса электронной почты. Есть ли что-то, чего я забываю?

from django.conf import settings
from django.contrib.auth.models import User

class EmailOrUsernameModelBackend(object):
    """
    This is a ModelBacked that allows authentication with either a username or an email address.

    """
    def authenticate(self, username=None, password=None):
        if '@' in username:
            kwargs = {'email': username}
        else:
            kwargs = {'username': username}
        try:
            user = User.objects.get(**kwargs)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

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

Изменить: Как я уже сказал, унаследовал от ModelBackend и установил его в моих настройках В моих настройках у меня есть это   AUTHENTICATION_BACKENDS = (       'users.backends',       'Django.contrib.auth.backends.ModelBackend',       ) И я поменял backend на это:

from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend
class EmailOrUsernameModelBackend(ModelBackend):
    """
    This is a ModelBacked that allows authentication with either a username or an email address.

    """
    def authenticate(self, username=None, password=None):
        if '@' in username:
            kwargs = {'email': username}
        else:
            kwargs = {'username': username}
        try:
            user = User.objects.get(**kwargs)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

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

Теперь я получаю ошибку Module "users" does not define a "backends" attribute/class.

Ответ 1

После выполнения рекомендаций, приведенных выше, и изменения AUTHENTICATION_BACKENDS = ['yourapp.yourfile.EmailOrUsernameModelBackend'], я получил ошибку Manager isn't available; User has been swapped for 'users.User'. Это было вызвано тем, что я использовал модель пользователя по умолчанию вместо моей собственной. Вот рабочий код.

from django.conf import settings
from django.contrib.auth import get_user_model

class EmailOrUsernameModelBackend(object):
    """
    This is a ModelBacked that allows authentication with either a username or an email address.

    """
    def authenticate(self, username=None, password=None):
        if '@' in username:
            kwargs = {'email': username}
        else:
            kwargs = {'username': username}
        try:
            user = get_user_model().objects.get(**kwargs)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

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

Ответ 2

Еще одно решение:

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


class EmailOrUsernameModelBackend(ModelBackend):
    """
    Authentication backend which allows users to authenticate using either their
    username or email address

    Source: https://stackoverflow.com/a/35836674/59984
    """

    def authenticate(self, request, username=None, password=None, **kwargs):
        # n.b. Django <2.1 does not pass the 'request'

        user_model = get_user_model()

        if username is None:
            username = kwargs.get(user_model.USERNAME_FIELD)

        # The 'username' field is allows to contain '@' characters so
        # technically a given email address could be present in either field,
        # possibly even for different users, so we'll query for all matching
        # records and test each one.
        users = user_model._default_manager.filter(
            Q(**{user_model.USERNAME_FIELD: username}) | Q(email__iexact=username)
        )

        # Test whether any matched user has the provided password:
        for user in users:
            if user.check_password(password):
                return user
        if not users:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (see
            # https://code.djangoproject.com/ticket/20760)
            user_model().set_password(password)

Исправления:

  • По умолчанию @ не запрещено в поле имени пользователя, поэтому, если пользовательская модель пользователя не запрещает символ @, его нельзя использовать для различения имени пользователя и электронной почты.
  • Технически, два пользователя могут использовать один и тот же адрес электронной почты, один в поле электронной почты, другой в имени пользователя. Если такая возможность не ограничена, это может привести либо к невозможности аутентификации пользователя, либо к необработанному исключению MultipleObjectsReturned если используется UserModel._default_manager.get(Q(username__iexact=username) | Q(email__iexact=username)).
  • Поймать любое исключение, except: это вообще плохая практика

Недостаток - если два пользователя используют один и тот же адрес электронной почты, один в имени пользователя, другой - в электронной почте, и у них один и тот же пароль, то он склонен к аутентификации первого совпадения. Я предполагаю, что шансы на это крайне маловероятны.

Также обратите внимание: любой из подходов должен обеспечивать применение уникального поля email в модели User, поскольку модель User по умолчанию не определяет уникальную электронную почту, что может привести к необработанному исключению в случае User.objects.get(email__iexact="...") или используется для проверки подлинности первого совпадения. В любом случае, использование электронной почты для входа в систему предполагает, что электронная почта является уникальной.

Ответ 3

Мне показалось, что я попробовал свой более простой подход для всех, кто сталкивался с этим:

# -*- coding: utf-8 -*-
from django.contrib.auth import backends, get_user_model
from django.db.models import Q


class ModelBackend(backends.ModelBackend):
    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()

        try:
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=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)

Примечание:

  • игнорирует USERNAME_FIELD, хотя вы можете добавить его обратно довольно легко.
  • регистр нечувствителен (вы можете просто удалить __iexact, но не делать этого)

Ответ 4

Я знаю, что на этот вопрос уже дан ответ, однако я нашел реальный изящный способ реализовать вход с использованием электронной почты и имени пользователя с использованием представлений аутентификации Django. Я не видел, чтобы кто-то использовал этот тип метода, поэтому я решил поделиться им для простоты.

from django.contrib.auth.models import User


class EmailAuthBackend():
    def authenticate(self, username=None, password=None):
        try:
            user = User.objects.get(email=username)
            if user.check_password(raw_password=password):
                return user
            return None
        except User.DoesNotExist:
            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 = (
    'django.contrib.auth.backends.ModelBackend',
    'myapp.authentication.EmailAuthBackend',
)

Ответ 5

Обновлена ​​версия того же фрагмента, с улучшенной безопасностью. Кроме того, он позволяет включать или отключать проверку подлинности с учетом регистра. Если вы предпочитаете, вы можете установить его прямо из pypi.

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

###################################
"""  DEFAULT SETTINGS + ALIAS   """
###################################


try:
    am = settings.AUTHENTICATION_METHOD
except:
    am = 'both'
try:
    cs = settings.AUTHENTICATION_CASE_SENSITIVE
except:
    cs = 'both'

#####################
"""   EXCEPTIONS  """
#####################


VALID_AM = ['username', 'email', 'both']
VALID_CS = ['username', 'email', 'both', 'none']

if (am not in VALID_AM):
    raise Exception("Invalid value for AUTHENTICATION_METHOD in project "
                    "settings. Use 'username','email', or 'both'.")

if (cs not in VALID_CS):
    raise Exception("Invalid value for AUTHENTICATION_CASE_SENSITIVE in project "
                    "settings. Use 'username','email', 'both' or 'none'.")

############################
"""  OVERRIDDEN METHODS  """
############################


class DualAuthentication(ModelBackend):
    """
    This is a ModelBacked that allows authentication
    with either a username or an email address.
    """

    def authenticate(self, username=None, password=None):
        UserModel = get_user_model()
        try:
            if ((am == 'email') or (am == 'both')):
                if ((cs == 'email') or cs == 'both'):
                    kwargs = {'email': username}
                else:
                    kwargs = {'email__iexact': username}

                user = UserModel.objects.get(**kwargs)
            else:
                raise
        except:
            if ((am == 'username') or (am == 'both')):
                if ((cs == 'username') or cs == 'both'):
                    kwargs = {'username': username}
                else:
                kwargs = {'username__iexact': username}

                user = UserModel.objects.get(**kwargs)
        finally:
            try:
                if user.check_password(password):
                    return user
            except:
                # Run the default password hasher once to reduce the timing
                # difference between an existing and a non-existing user.
                UserModel().set_password(password)
                return None

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

Ответ 6

Я написал код для этого в 2 простых шага:

  1. VIEWS.py
     if request.method == 'POST':
            userinput = request.POST['username']

            try:
                username = userbase.objects.get(email=userinput).username
            except userbase.DoesNotExist:
                username = request.POST['username']
            password = request.POST['password']
  1. INDEX.html

    Я создал 2 поля ввода, из которых 1-е для имени пользователя/электронной почты. Я беру любой ввод данных и пытаюсь найти те же данные в столбце электронной почты db, если он совпадает, я возвращаю имя пользователя и затем пытаюсь аутентифицироваться, если нет, я использую ввод непосредственно как имя пользователя.

Я использую Django 2.2

Ответ 7

Здесь выполняется обход, который не требует вообще модифицировать сервер аутентификации.

Сначала рассмотрим пример входа в систему из Django.

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

Если аутентификация с именем пользователя не удалась, мы можем проверить, есть ли соответствие по электронной почте, получить соответствующее имя пользователя и повторить проверку подлинности.

from django.contrib.auth import authenticate, login, get_user_model

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is None:
        User = get_user_model()
        user_queryset = User.objects.all().filter(email__iexact=username)
        if user_queryset:
            username = user_queryset[0].username
            user = authenticate(username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

Как и в примере с 1bit0fMe, электронное письмо должно быть уникальным полем и есть тот же (крайне маловероятный) недостаток, о котором они говорили.

Я бы рекомендовал этот подход только в том случае, если весь вход на ваш сайт обрабатывается одним представлением или формой. В противном случае было бы лучше изменить сам метод authenticate() в бэкэнд, чтобы избежать создания нескольких точек потенциального отказа.

Ответ 8

Предполагая, что вы заблокировали/запретили использование имени пользователя, имеющего @, и вы хотите использовать модель пользователя django.

if request.method == 'POST':
    form = LoginForm(request.POST)
    if form.is_valid():
        cd=form.cleaned_data
        if '@' in cd['username']:
            username=User.objects.get(email=cd['username']).username
        else:
            username=cd['username']

        user = authenticate(username=username,
                                password=cd['password'])

        if user is not None and user.is_active:
            login(request,user)
            return redirect('loggedin')
        else:
            return render(request, 'login.html')

Ответ 9

если вы используете django-rest-auth, то опция аутентификации с адресом электронной почты встроена и может конфликтовать с другими предлагаемыми методами. Вам просто нужно добавить следующее в settings.py:

ACCOUNT_AUTHENTICATION_METHOD = 'username_email'
ACCOUNT_EMAIL_REQUIRED = False
ACCOUNT_USERNAME_REQUIRED = False

#Following is added to enable registration with email instead of username
AUTHENTICATION_BACKENDS = (
 # Needed to login by username in Django admin, regardless of 'allauth'
 "django.contrib.auth.backends.ModelBackend",

 # 'allauth' specific authentication methods, such as login by e-mail
 "allauth.account.auth_backends.AuthenticationBackend",
)

Django отдыхает по электронной почте вместо имени пользователя https://django-allauth.readthedocs.io/en/latest/configuration.html

Обратите внимание, что если вы не хотите иметь одно поле, в которое пользователь может ввести имя пользователя или адрес электронной почты, вам придется проделать некоторую работу во внешнем интерфейсе, чтобы решить, следует ли отправлять запрос на вход в систему в качестве электронной почты, пароля или имени пользователя, пароль. Я сделал простой тест, содержит ли запись пользователя '@' с '.' далее. Я думаю, что кто-то намеренно создает имя пользователя, которое выглядит как адрес электронной почты, но не является его адресом электронной почты, - маловероятно, что я его не поддерживаю.

Ответ 10

Обратите внимание, что для большинства подобных решений вы должны добавить проверку уникальности электронной почты для модели User, чтобы избежать уязвимости аутентификации

# models.py
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'

и затем вам нужно обновить settings.py, определив свойство AUTH_USER_MODEL

AUTH_USER_MODEL = '[your_app_name].User'