J'essaie de créer un backend d'authentification pour permettre à mes utilisateurs de se connecter en utilisant soit leur adresse email, soit leur nom d'utilisateur dans Django 1.6 avec un modèle utilisateur personnalisé. Le backend fonctionne lorsque je me connecte avec un nom d'utilisateur, mais pour une raison quelconque ne le fait pas avec un email. Y a-t-il quelque chose que j'oublie de faire?
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
Edit: Comme suggéré, j'ai hérité de ModelBackend et je l'ai installé dans mes paramètres . Dans mes paramètres, j'ai ceci: 'Django.contrib.auth.backends.ModelBackend', ) Et j'ai changé le backend en ceci:
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
Maintenant, je reçois une erreur Module "users" does not define a "backends" attribute/class
.
Après avoir suivi les conseils donnés ci-dessus et modifié AUTHENTICATION_BACKENDS = ['yourapp.yourfile.EmailOrUsernameModelBackend']
, l'erreur Manager isn't available; User has been swapped for 'users.User'
m'échappait. Cela était dû au fait que j'utilisais le modèle utilisateur par défaut au lieu de mon propre modèle personnalisé. Voici le code de travail.
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
Encore une autre solution:
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)
Correctifs:
@
n'est pas interdit dans le champ du nom d'utilisateur. Par conséquent, à moins que le modèle utilisateur personnalisé interdise le symbole @
, il ne peut pas être utilisé pour distinguer le nom d'utilisateur et l'adresse de messagerie.MultipleObjectsReturned
non gérée si UserModel._default_manager.get(Q(username__iexact=username) | Q(email__iexact=username))
est utilisé.except:
est généralement une mauvaise pratiqueInconvénient: s'il y a deux utilisateurs qui utilisent le même courrier électronique, l'un dans le nom d'utilisateur, l'autre dans le courrier électronique, et qu'ils ont le même mot de passe, il est alors probable que le premier match soit authentifié. Je suppose que les chances de cela sont hautement improbables.
Remarque: toutes les approches doivent appliquer le champ unique email
dans le modèle utilisateur, car le modèle utilisateur par défaut ne définit pas de courrier électronique unique, ce qui conduirait à une exception non gérée si User.objects.get(email__iexact="...")
est utilisé ou à l'authentification de la première correspondance. . Dans tous les cas, utiliser un email pour se connecter suppose que cet email est unique.
Je pensais que je mettrais mon approche plus simple pour quiconque se heurterait à ceci:
# -*- 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)
Remarque:
USERNAME_FIELD
, bien que vous puissiez l'ajouter assez facilement__iexact
pour ne pas le faire)Je sais que cela a déjà été répondu, mais j’ai trouvé un moyen très pratique d’implémenter la connexion avec un courrier électronique et un nom d’utilisateur à l’aide des vues d’authentification de Django. Je n'ai vu personne utiliser ce type de méthode, alors j'ai pensé la partager pour des raisons de simplicité.
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
Puis dans votre settings.py ajouter ceci
AUTHENTICATION_BACKENDS = (
'Django.contrib.auth.backends.ModelBackend',
'myapp.authentication.EmailAuthBackend',
)
Version mise à jour du même extrait de code, avec une sécurité améliorée. En outre, il vous permet d'activer ou de désactiver l'authentification sensible à la casse. Si vous préférez, vous pouvez l'installer directement à partir de 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
si vous utilisez Django-rest-auth, l'option d'authentification avec l'adresse de messagerie est intégrée et peut entrer en conflit avec les autres méthodes proposées. Il vous suffit d’ajouter ce qui suit à 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 rest auth email au lieu de nom d'utilisateurhttps://Django-allauth.readthedocs.io/en/latest/configuration.html
Notez que, sauf si vous souhaitez avoir une seule boîte dans laquelle l'utilisateur peut taper un nom d'utilisateur ou une adresse e-mail, vous devrez effectuer quelques opérations frontales pour décider d'envoyer la demande de connexion sous forme d'e-mail, de mot de passe ou de nom d'utilisateur. mot de passe. J'ai fait un test simple pour savoir si l'entrée de l'utilisateur contenait un "@" avec un "." plus loin. Je pense que quelqu'un qui crée délibérément un nom d'utilisateur qui ressemble à une adresse électronique (mais ce n'est pas son adresse électronique) est assez improbable pour que je ne la soutienne pas.
Voici une solution de contournement qui ne nécessite aucune modification du backend d’authentification.
Commençons par regarder la vue de connexion example de 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.
...
Si l'authentification avec le nom d'utilisateur échoue, nous pouvons vérifier s'il existe une correspondance électronique, obtenir le nom d'utilisateur correspondant et essayer de vous authentifier à nouveau.
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.
...
Semblable à l'exemple de 1bit0fMe, le courrier électronique doit constituer un champ unique et présenter les mêmes inconvénients (très improbables) qu'ils ont mentionnés.
Je ne recommanderais cette approche que si toutes les connexions sur votre site sont gérées par une vue ou un formulaire unique. Sinon, il serait préférable de modifier la méthode authenticate () elle-même dans le backend pour éviter de créer plusieurs points d'échec potentiel.
En supposant que vous ayez bloqué/interdit le nom d'utilisateur avec un @ et que vous souhaitiez utiliser le modèle Utilisateur 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')