Custom authentication backend works but doens't work with Django Rest's Token Authentication

huangapple go评论61阅读模式
英文:

Custom authentication backend works but doens't work with Django Rest's Token Authentication

问题

我的用户模型有一个电子邮件字段和一个用户名字段。USERNAME_FIELD被设置为电子邮件。但是我编写了一个身份验证后端,以支持使用用户名登录:

class UsernameAuthBackend(BaseBackend):
    def authenticate(self, request, email=None, password=None):
        try:
            user = User.objects.get(username=email)
            if user.check_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",
    "accounts.authentication.UsernameAuthBackend",
]

这个方法可以正常工作,但是我无法使用用户名来获取用户的令牌(也无法在Django的管理面板上工作)。

英文:

My User model has an email field and a username field. `USERNAME_FIELD' is set to email. But I wrote an authentication backend to also support logging in using username:

class UsernameAuthBackend(BaseBackend):
    def authenticate(self, request, email=None, password=None):
        try:
            user = User.objects.get(username=email)
            if user.check_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",
    "accounts.authentication.UsernameAuthBackend",
]

This works fine but it I can't use usernames with obtain_auth_token to get a user's token. (It also doesn't work on Django's admin panel.)

答案1

得分: 1

DRF

覆盖序列化器可以提供一种更简洁的方式来调整obtain_auth_token的身份验证数据输入。这是因为实际的身份验证逻辑通常在序列化器内部处理。

以下是如何创建一个自定义序列化器来处理电子邮件和用户名的方法:

  1. 自定义身份验证序列化器
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework import serializers
from django.contrib.auth import authenticate

class CustomAuthTokenSerializer(AuthTokenSerializer):
    username = serializers.CharField(label="Username or Email")

    def validate(self, attrs):
        username_or_email = attrs.get('username')
        password = attrs.get('password')

        # 首先尝试使用电子邮件进行身份验证
        try:
            user = User.objects.get(email=username_or_email)
        except User.DoesNotExist:
            # 如果电子邮件不存在,则尝试使用用户名进行身份验证
            try:
                user = User.objects.get(username=username_or_email)
            except User.DoesNotExist:
                msg = '无法使用提供的凭据登录。'
                raise serializers.ValidationError(msg, code='authorization')

        if not user.check_password(password):
            msg = '无法使用提供的凭据登录。'
            raise serializers.ValidationError(msg, code='authorization')

        attrs['user'] = user
        return attrs
  1. 更新CustomObtainAuthToken视图以使用自定义序列化器
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from .serializers import CustomAuthTokenSerializer  # 调整导入路径

class CustomObtainAuthToken(ObtainAuthToken):
    serializer_class = CustomAuthTokenSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        return Response({'token': token.key})

通过这种方式,在序列化器的validate方法中处理确定输入是用户名还是电子邮件的逻辑,使视图更简单,并与Django REST framework的模式更一致。

Django Admin

您可以使用django-username-email包或覆盖默认的身份验证表单以允许基于用户名的身份验证。以下是一种基本的方法:

from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import authenticate

class CustomAdminAuthForm(AuthenticationForm):
    def clean(self):
        username_or_email = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username_or_email and password:
            self.user_cache = authenticate(self.request, email=username_or_email, password=password)
            if self.user_cache is None:
                raise forms.ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )
        return self.cleaned_data

# 更新您的admin.py
from django.contrib import admin
from .forms import CustomAdminAuthForm

admin.site.login_form = CustomAdminAuthForm

请注意,上述代码片段更多是概念性的,而不是确切的代码。

英文:

DRF

You can overriding the serializer can provide a cleaner way to adjust the authentication data input for obtain_auth_token. This is because the actual authentication logic is often handled within the serializer.

Here's how you can create a custom serializer to handle both email and username:

  1. Custom Authentication Serializer:
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework import serializers
from django.contrib.auth import authenticate

class CustomAuthTokenSerializer(AuthTokenSerializer):
    username = serializers.CharField(label="Username or Email")

    def validate(self, attrs):
        username_or_email = attrs.get('username')
        password = attrs.get('password')

        # Try authentication with email first
        try:
            user = User.objects.get(email=username_or_email)
        except User.DoesNotExist:
            # If email doesn't exist, check with username
            try:
                user = User.objects.get(username=username_or_email)
            except User.DoesNotExist:
                msg = 'Unable to log in with provided credentials.'
                raise serializers.ValidationError(msg, code='authorization')

        if not user.check_password(password):
            msg = 'Unable to log in with provided credentials.'
            raise serializers.ValidationError(msg, code='authorization')

        attrs['user'] = user
        return attrs
  1. Update CustomObtainAuthToken View to use the Custom Serializer:
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from .serializers import CustomAuthTokenSerializer  # Adjust the import path

class CustomObtainAuthToken(ObtainAuthToken):
    serializer_class = CustomAuthTokenSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        return Response({'token': token.key})

This way, you're handling the logic of determining whether the input is a username or email in the serializer's validate method, making the view simpler and more consistent with Django REST framework's patterns.

Django Admin

You can use django-username-email package or override the default authentication form to allow for username-based authentication. Here's a basic approach:

from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import authenticate

class CustomAdminAuthForm(AuthenticationForm):
    def clean(self):
        username_or_email = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username_or_email and password:
            self.user_cache = authenticate(self.request, email=username_or_email, password=password)
            if self.user_cache is None:
                raise forms.ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )
        return self.cleaned_data

# Update your admin.py
from django.contrib import admin
from .forms import CustomAdminAuthForm

admin.site.login_form = CustomAdminAuthForm

Please note that the above code snippets are more conceptual than exact code.

huangapple
  • 本文由 发表于 2023年8月9日 01:59:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76862111.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定