web-dev-qa-db-fra.com

Connectez-vous en utilisant une adresse e-mail ou un nom d'utilisateur dans Django.

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.

17
user3282276

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
8
user3282276

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:

  • Par défaut, @ 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.
  • Techniquement, il peut y avoir deux utilisateurs utilisant le même email, l'un dans le champ email, l'autre dans le nom d'utilisateur. À moins que cette possibilité ne soit restreinte, un utilisateur peut ne pas être en mesure de s'authentifier ou une exception MultipleObjectsReturned non gérée si UserModel._default_manager.get(Q(username__iexact=username) | Q(email__iexact=username)) est utilisé.
  • Traquer toute exception avec except: est généralement une mauvaise pratique

Inconvé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.

13
1bit0fMe

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:

  • ignore USERNAME_FIELD, bien que vous puissiez l'ajouter assez facilement
  • insensible à la casse (vous pouvez simplement enlever le __iexact pour ne pas le faire)
3
Steve

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

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
2
Adrian Lopez

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.

0
Little Brain

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.

0
John Meinken

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')
0
George Battaglia