Почему аутентификация в drf не работает и как мне заставить ее работать?
Я использую Django с rest framework. Регистрация работает нормально, но когда я вхожу в систему, после передачи адреса электронной почты и пароля серверной части, authenticate()
возвращает None, даже если учетные данные верны. Я использую пользовательскую модель пользователя. Вот соответствующие строки кода:
models.py (не включает все поля)
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, **kwargs):
if not email:
raise ValueError('The user must enter a valid email.')
email = self.normalize_email(email)
user = self.model(email=email, **kwargs)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, **kwargs):
kwargs.setdefault('is_staff', True)
kwargs.setdefault('is_superuser', True)
kwargs.setdefault('is_active', True)
if kwargs.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if kwargs.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self.create_user(email, password, **kwargs)
class CustomUser(AbstractBaseUser, PermissionsMixin):
user_id = models.AutoField(primary_key=True)
first_name = models.CharField(max_length=255, blank=True)
middle_name = models.CharField(max_length=255, blank=True)
last_name = models.CharField(max_length=255, blank=True)
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
groups = models.ManyToManyField(
"auth.Group",
related_name="customuser_set",
blank=True
)
user_permissions = models.ManyToManyField(
"auth.Permission",
related_name="customuser_set",
blank=True
)
viewsets.py
@action(detail=False, methods=["post"], permission_classes=[AllowAny])
def login(self, request):
email = request.data.get("email")
password = request.data.get("password")
user = CustomUser.objects.get(email=email)
print(user)
user = authenticate(request, email=email, password=password) # this returns None all the time
if user:
login(request, user)
return Response({"message": "Login successful!"})
return Response({"error": "Invalid credentials"}, status=401)
settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
]
Я не уверен, в чем проблема. Выполнение user = CustomUser.objects.get(email=email)
также не приводит к появлению экземпляра CustomUser
.
Внутри вашего приложения Django создайте файл authentication.py
и добавьте:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
User = get_user_model()
class EmailBackend(ModelBackend):
"""
Custom authentication backend to authenticate users using their email and password.
"""
def authenticate(self, request, email=None, password=None, **kwargs):
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return None
if user.check_password(password):
return user
return None
Измените список AUTHENTICATION_BACKENDS
в settings.py
:
AUTHENTICATION_BACKENDS = [
'your_app.authentication.EmailBackend', # Custom authentication backend
'django.contrib.auth.backends.ModelBackend', # Default authentication backend
]
Измените метод входа в систему в вашем наборе представлений:
@action(detail=False, methods=["post"], permission_classes=[AllowAny])
def login(self, request):
email = request.data.get("email")
password = request.data.get("password")
user = authenticate(request, email=email, password=password) # Now should work
if user:
login(request, user)
return Response({"message": "Login successful!"})
return Response({"error": "Invalid credentials"}, status=401)
И теперь вы можете протестировать с помощью:
user = CustomUser.objects.get(email=email)
print(user.check_password(password)) # Should return True if the password is correct
Серверная часть аутентификации Django по умолчанию (ModelBackend) ожидает, что в качестве идентификатора будет использоваться имя пользователя. Однако, когда пользовательская модель пользователя определяет электронную почту как поле USERNAME_FIELD, Django не распознает ее во время аутентификации.
Чтобы исправить это, необходимо внедрить пользовательский сервер аутентификации, как справедливо подчеркнуто @berlin выше.
Шаг 1: Создайте пользовательский сервер аутентификации
Создайте новый файл в соответствующем приложении (например, users/auth_backend.py) и добавьте:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
UserModel = get_user_model()
class EmailBackend(ModelBackend):
"""
Custom authentication backend to authenticate users using email instead of username.
"""
def authenticate(self, request, email=None, password=None, **kwargs):
if email is None or password is None:
return None
try:
user = UserModel.objects.get(email=email)
except UserModel.DoesNotExist:
return None
if user.check_password(password) and self.user_can_authenticate(user):
return user
return None
Шаг 2: Зарегистрируйте пользовательский сервер аутентификации
Изменить settings.py чтобы указать Django использовать этот пользовательский сервер аутентификации:
AUTHENTICATION_BACKENDS = [
'users.auth_backend.EmailBackend', # Adjust path based on the project structure
'django.contrib.auth.backends.ModelBackend', # Keep Django's default
]
Зачем сохранять ModelBackend?
Если аутентификация Django по умолчанию (например, вход администратора с использованием имени пользователя) все еще требуется, рекомендуется использовать ModelBackend в качестве запасного варианта.
Шаг 3: Обновите функцию входа в систему
Измените представление, обрабатывающее аутентификацию, чтобы использовать новый сервер:
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import update_last_login
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
@action(detail=False, methods=["post"], permission_classes=[AllowAny])
def login(self, request):
email = request.data.get("email")
password = request.data.get("password")
user = authenticate(request, email=email, password=password) # Now works correctly
if user:
login(request, user)
update_last_login(None, user) # Updates last login timestamp
return Response({"message": "Login successful!"})
return Response({"error": "Invalid credentials"}, status=401)
Шаг 4: Перезапустите сервер Django
После внесения этих изменений перезапустите Django, чтобы убедиться, что применен новый сервер аутентификации:
python manage.py migrate
python manage.py runserver
Ожидаемый результат:
- проверка подлинности (запрос, электронная почта=email, пароль=password) вернет экземпляр пользователя корректно.
- Пользователи смогут без проблем войти в систему, используя свой адрес электронной почты и пароль.
- Серверная часть проверки подлинности теперь будет работать должным образом для входа в систему по электронной почте.
Дополнительные советы по отладке
Если вход в систему по-прежнему не удается, проверьте следующее:
- Убедитесь, что выполнены миграции (python manage.py migrate)
- Подтвердите, что электронное письмо существует в базе данных
- Используйте print(user.check_password(пароль)) в серверной части для проверки хэширования пароля
- Очистить сеансы Django (python manage.py shell → из django.contrib.sessions.models импортировать сеанс; Session.objects.all().удалить())
Это решение было проверено с помощью искусственного интеллекта, но протестировано и доработано вручную.