Войдите в систему, используя либо адрес электронной почты, либо имя пользователя в 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 простых шага:
-
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']
-
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'