Настройка аутентификации в Django
Аутентификация, входящая в состав Django, подходит для большинства распространённых случаев, но ваши потребности могут не соответствовать настройкам по умолчанию. Чтобы настроить аутентификацию в своих проектах, нужно понимать, какие части предоставленной системы можно расширить или заменить. В этом документе подробно описано, как можно настроить систему аутентификации.
Бэкенды аутентификации предоставляют расширяемую систему на случай, когда имя пользователя и пароль, хранящиеся вместе с моделью пользователя, нужно проверять с помощью службы, отличной от используемой по умолчанию в Django.
Вы можете назначить своим моделям пользовательские разрешения, которые можно проверять с помощью системы авторизации Django.
Вы можете расширить стандартную модель User или заменить её полностью настроенной моделью.
Другие источники аутентификации
Иногда возникает необходимость подключить другой источник аутентификации — то есть другой источник имён пользователей и паролей или других методов аутентификации.
Например, в вашей компании уже может быть настроен LDAP, в котором хранятся имя пользователя и пароль каждого сотрудника. Наличие отдельных учётных записей в LDAP и приложениях на Django стало бы обременительным как для сетевого администратора, так и для самих пользователей.
Чтобы справляться с подобными ситуациями, система аутентификации Django позволяет подключать другие источники аутентификации. Вы можете переопределить стандартную схему Django на основе базы данных или использовать стандартную систему совместно с другими системами.
Информацию о бэкендах аутентификации, входящих в состав Django, см. в справочнике по бэкендам аутентификации.
Указание бэкендов аутентификации
Внутри Django поддерживает список «бэкендов аутентификации», которые проверяются при аутентификации. Когда кто-либо вызывает django.contrib.auth.authenticate() — как описано в разделе Как выполнить вход пользователя, — Django пытается выполнить аутентификацию с помощью всех своих бэкендов. Если первый метод аутентификации не срабатывает, Django пробует второй и так далее, пока не проверит все бэкенды.
Список используемых бэкендов аутентификации задаётся параметром AUTHENTICATION_BACKENDS. Это должен быть список путей Python, указывающих на классы Python, которые умеют выполнять аутентификацию. Эти классы могут находиться где угодно в пути Python.
По умолчанию параметру AUTHENTICATION_BACKENDS присваивается значение:
["django.contrib.auth.backends.ModelBackend"]
Это базовый бэкенд аутентификации, который проверяет базу данных пользователей Django и выполняет запросы к встроенным разрешениям. Он не обеспечивает защиту от атак методом перебора с помощью ограничения частоты запросов. Вы можете реализовать собственный механизм ограничения частоты запросов в пользовательском бэкенде аутентификации или использовать механизмы, предоставляемые большинством веб-серверов.
Порядок элементов в AUTHENTICATION_BACKENDS имеет значение: если одни и те же имя пользователя и пароль действительны в нескольких бэкендах, Django прекратит обработку при первом успешном совпадении.
Если бэкенд вызывает исключение PermissionDenied, аутентификация немедленно завершится неудачей. Django не будет проверять следующие бэкенды.
Примечание
После успешной аутентификации пользователя Django сохраняет в его сеансе сведения о том, какой бэкенд использовался, и повторно использует тот же бэкенд в течение всего сеанса, когда требуется доступ к текущему аутентифицированному пользователю. Это фактически означает, что источники аутентификации кэшируются отдельно для каждого сеанса. Поэтому при изменении AUTHENTICATION_BACKENDS, если вы хотите заставить пользователей пройти аутентификацию другими способами, потребуется очистить данные сеансов. Для этого можно выполнить Session.objects.all().delete().
Создание бэкенда аутентификации
Бэкенд аутентификации — это класс, реализующий два обязательных метода: get_user(user_id) и authenticate(request, **credentials), а также набор необязательных методов авторизации, связанных с разрешениями.
Метод get_user принимает user_id — это может быть имя пользователя, идентификатор в базе данных или что-либо ещё, но он должен быть первичным ключом объекта пользователя — и возвращает объект пользователя или None.
Метод authenticate принимает аргумент request и учётные данные в виде именованных аргументов. Чаще всего он выглядит так:
from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
# Check the username/password and return a user.
...
Однако он также может выполнять аутентификацию по токену, например:
from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, token=None):
# Check the token and return a user.
...
В любом случае authenticate() должен проверять полученные учётные данные и возвращать соответствующий им объект пользователя, если они действительны. Если они недействительны, метод должен возвращать None.
request — это объект HttpRequest, который может иметь значение None, если он не был передан в authenticate() (который передаёт его бэкенду).
Панель администратора Django тесно связана с объектом пользователя Django. Например, чтобы пользователь мог получить доступ к панели администратора, значения User.is_staff и User.is_active должны быть равны True (подробности см. в AdminSite.has_permission()).
Лучше всего для каждого пользователя, существующего в вашем бэкенде (например, в каталоге LDAP или во внешней базе данных SQL), создать объект Django User. Вы можете заранее написать для этого скрипт или создать его в методе authenticate при первом входе пользователя в систему.
Ниже приведён пример бэкенда, который выполняет аутентификацию по имени пользователя и паролю, заданным в файле settings.py, и при первой успешной аутентификации пользователя создаёт объект Django User. В этом примере созданный объект Django User является суперпользователем, который получит полный доступ к панели администратора:
from django.conf import settings
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
class SettingsBackend(BaseBackend):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
Use the login name and a hash of the password. For example:
ADMIN_LOGIN = 'admin'
ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
"""
def authenticate(self, request, username=None, password=None):
login_valid = settings.ADMIN_LOGIN == username
pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
if login_valid and pwd_valid:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# Create a new user. There's no need to set a password
# because only the password from settings.py is checked.
user = User(username=username) # is_active defaults to True.
user.is_staff = True
user.is_superuser = True
user.save()
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Пользовательские разрешения
Чтобы создать пользовательские разрешения для объекта определённой модели, используйте permissions — атрибут Meta модели.
В этом примере модель Task создаёт два пользовательских разрешения — то есть действия, которые пользователи могут или не могут выполнять с экземплярами Task, характерные для вашего приложения:
class Task(models.Model):
...
class Meta:
permissions = [
("change_task_status", "Can change the status of tasks"),
("close_task", "Can remove a task by setting its status as closed"),
]
Это лишь создаёт дополнительные разрешения при запуске manage.py migrate (функция создания разрешений подключена к сигналу post_migrate). Ваш код должен проверять значение этих разрешений, когда пользователь пытается получить доступ к функциональности приложения (изменить статус задач или закрыть задачи). Продолжая приведённый выше пример, следующий код проверяет, может ли пользователь закрывать задачи:
user.has_perm("app.close_task")
Расширение существующей модели User
Существует два способа расширить стандартную модель User, не заменяя её собственной моделью. Если требуемые изменения касаются только поведения и не требуют менять данные, хранящиеся в базе данных, можно создать прокси-модель на основе User. Это позволяет использовать любые возможности прокси-моделей, включая сортировку по умолчанию, пользовательские менеджеры и пользовательские методы модели.
Если вы хотите хранить информацию, связанную с User, можно использовать OneToOneField для связи с моделью, содержащей дополнительные поля. Такую модель «один к одному» часто называют моделью профиля, поскольку в ней может храниться информация о пользователе сайта, не относящаяся к аутентификации. Например, можно создать модель Employee:
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(max_length=100)
Предположим, что существует сотрудник Fred Smith, для которого созданы модели User и Employee. Доступ к связанной информации можно получить с помощью стандартных соглашений Django для связанных моделей:
>>> u = User.objects.get(username="fsmith") >>> freds_department = u.employee.department
Чтобы добавить поля модели профиля на страницу пользователя в панели администратора, определите InlineModelAdmin (в этом примере мы используем StackedInline) в файле admin.py вашего приложения и добавьте его в класс UserAdmin, зарегистрированный для класса User:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from my_user_profile_app.models import Employee
# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
model = Employee
can_delete = False
verbose_name_plural = "employee"
# Define a new User admin
class UserAdmin(BaseUserAdmin):
inlines = [EmployeeInline]
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Эти модели профилей не обладают какими-либо особыми свойствами — это обычные модели Django, связанные с моделью пользователя отношением «один к одному». Поэтому они не создаются автоматически при создании пользователя, однако для создания или обновления связанных моделей при необходимости можно использовать сигнал django.db.models.signals.post_save.
Использование связанных моделей приводит к дополнительным запросам или объединениям для получения связанных данных. В зависимости от ваших потребностей лучшим вариантом может оказаться пользовательская модель пользователя, содержащая связанные поля. Однако уже существующие связи со стандартной моделью пользователя в приложениях вашего проекта могут оправдывать дополнительную нагрузку на базу данных.
Подмена пользовательской модели User
Для некоторых типов проектов могут существовать требования к аутентификации, которым встроенная модель Django User не всегда соответствует. Например, на некоторых сайтах в качестве идентификатора удобнее использовать адрес электронной почты, а не имя пользователя.
Django позволяет переопределить пользовательскую модель по умолчанию, указав значение параметра AUTH_USER_MODEL, ссылающегося на пользовательскую модель:
AUTH_USER_MODEL = "myapp.MyUser"
Эта пара, разделённая точкой, описывает label приложения Django (которое должно быть в INSTALLED_APPS) и имя модели Django, которую вы хотите использовать в качестве пользовательской модели.
Использование пользовательской модели при создании проекта
Если вы создаёте новый проект, можно настроить пользовательскую модель, которая будет вести себя так же, как модель пользователя по умолчанию, унаследовав её от AbstractUser:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
Не забудьте указать её в параметре AUTH_USER_MODEL. Сделайте это до создания миграций или первого запуска manage.py migrate.
Также зарегистрируйте модель в файле admin.py приложения:
from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import User admin.site.register(User, UserAdmin)
Переход на пользовательскую модель в ходе проекта
Изменить AUTH_USER_MODEL после создания таблиц базы данных возможно, но это может быть сложно, поскольку, например, затрагиваются внешние ключи и связи «многие ко многим».
Это изменение нельзя выполнить автоматически: потребуется вручную исправить схему, перенести данные из старой таблицы пользователей и, возможно, вручную повторно применить некоторые миграции. Общий план действий приведён в #25313.
Из-за ограничений динамических зависимостей Django для заменяемых моделей модель, указанная в AUTH_USER_MODEL, должна создаваться в первой миграции своего приложения (обычно называемой 0001_initial); в противном случае возникнут проблемы с зависимостями.
Кроме того, при выполнении миграций может возникнуть CircularDependencyError, поскольку из-за динамической зависимости Django не сможет автоматически разорвать цикл зависимостей. Если вы увидите эту ошибку, разорвите цикл, переместив модели, от которых зависит ваша пользовательская модель, во вторую миграцию. (Если хотите посмотреть, как обычно разрешается эта циклическая зависимость, можно попробовать создать две обычные модели со связью ForeignKey друг с другом и посмотреть, как makemigrations её разрешает.)
Повторно используемые приложения и AUTH_USER_MODEL
Повторно используемые приложения не должны реализовывать пользовательскую модель. В проекте может использоваться множество приложений, и два повторно используемых приложения, реализующих пользовательскую модель, нельзя будет использовать вместе. Если приложению нужно хранить информацию о пользователе, используйте ForeignKey или OneToOneField для settings.AUTH_USER_MODEL, как описано ниже.
Ссылки на модель User
Если вы напрямую ссылаетесь на User (например, указывая её во внешнем ключе), ваш код не будет работать в проектах, где параметр AUTH_USER_MODEL изменён и указывает на другую пользовательскую модель.
-
get_user_model()[исходный код] -
Вместо прямой ссылки на
Userследует ссылаться на пользовательскую модель с помощьюdjango.contrib.auth.get_user_model(). Этот метод возвращает текущую активную пользовательскую модель — пользовательскую модель, если она указана, илиUserв противном случае.При определении внешнего ключа или связи «многие ко многим» с пользовательской моделью следует указать пользовательскую модель с помощью параметра
AUTH_USER_MODEL. Например:from django.conf import settings from django.db import models class Article(models.Model): author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, )При подключении к сигналам, отправляемым пользовательской моделью, следует указать пользовательскую модель с помощью параметра
AUTH_USER_MODEL. Например:from django.conf import settings from django.db.models.signals import post_save def post_save_receiver(sender, instance, created, **kwargs): pass post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)Как правило, проще всего ссылаться на пользовательскую модель с помощью параметра
AUTH_USER_MODELв коде, выполняемом во время импорта. Однако можно также вызватьget_user_model()во время импорта моделей Django, поэтому можно использоватьmodels.ForeignKey(get_user_model(), ...).Если ваше приложение тестируется с несколькими пользовательскими моделями, например с использованием
@override_settings(AUTH_USER_MODEL=...), и результатget_user_model()кэшируется в переменной уровня модуля, может потребоваться обрабатывать сигналsetting_changed, чтобы очищать кэш. Например:from django.apps import apps from django.contrib.auth import get_user_model from django.core.signals import setting_changed from django.dispatch import receiver @receiver(setting_changed) def user_model_swapped(*, setting, **kwargs): if setting == "AUTH_USER_MODEL": apps.clear_cache() from myapp import some_module some_module.UserModel = get_user_model()
Выбор пользовательской модели
При создании проекта с пользовательской моделью следует сначала подумать, подходит ли этот выбор для вашего проекта.
Хранение всей информации о пользователе в одной модели избавляет от необходимости выполнять дополнительные или более сложные запросы к базе данных для получения связанных моделей. С другой стороны, информацию о пользователе, специфичную для приложения, может быть целесообразнее хранить в модели, связанной с пользовательской моделью. Это позволяет каждому приложению задавать собственные требования к пользовательским данным, не создавая потенциальных конфликтов и не нарушая предположения других приложений. Кроме того, пользовательская модель останется максимально простой и будет сосредоточена на аутентификации, соответствуя минимальным требованиям Django к пользовательским моделям.
При использовании серверной части аутентификации по умолчанию модель должна содержать одно уникальное поле, которое можно использовать для идентификации. Это может быть имя пользователя, адрес электронной почты или любой другой уникальный атрибут. Если используется пользовательская серверная часть аутентификации, поддерживающая неуникальные имена пользователей, поле имени пользователя может быть неуникальным.
Самый простой способ создать совместимую пользовательскую модель — унаследовать её от AbstractBaseUser. AbstractBaseUser предоставляет основную реализацию пользовательской модели, включая хеширование паролей и сброс паролей с помощью токенов. Затем необходимо указать некоторые ключевые детали реализации:
-
class models.CustomUser -
-
USERNAME_FIELD -
Строка с именем поля пользовательской модели, используемого в качестве уникального идентификатора. Обычно это имя пользователя, но им также может быть адрес электронной почты или любой другой уникальный идентификатор. Поле должно быть уникальным (например, в его определении должно быть задано
unique=True), если только вы не используете пользовательскую серверную часть аутентификации, поддерживающую неуникальные имена пользователей.В следующем примере для идентификации используется поле
identifier:class MyUser(AbstractBaseUser): identifier = models.CharField(max_length=40, unique=True) ... USERNAME_FIELD = "identifier"
-
EMAIL_FIELD -
Строка с именем поля адреса электронной почты модели
User. Это значение возвращается методомget_email_field_name().
-
REQUIRED_FIELDS -
Список имён полей, значения которых запрашиваются при создании пользователя с помощью команды управления
createsuperuser. Пользователю будет предложено ввести значение для каждого из этих полей. Список должен включать все поля, у которыхblankимеет значениеFalseили не задано, а также может включать дополнительные поля, значения которых требуется запрашивать при интерактивном создании пользователя.REQUIRED_FIELDSне влияет на другие части Django, например на создание пользователя в административной панели.Например, ниже приведено частичное определение пользовательской модели с двумя обязательными полями — датой рождения и ростом:
class MyUser(AbstractBaseUser): ... date_of_birth = models.DateField() height = models.FloatField() ... REQUIRED_FIELDS = ["date_of_birth", "height"]Примечание
REQUIRED_FIELDSдолжен содержать все обязательные поля вашей пользовательской модели, но не должен содержатьUSERNAME_FIELDилиpassword, поскольку значения этих полей запрашиваются всегда.
-
is_active -
Логический атрибут, указывающий, считается ли пользователь «активным». Этот атрибут предоставляется в
AbstractBaseUserи по умолчанию имеет значениеTrue. Способ его реализации зависит от особенностей выбранных серверных частей аутентификации. Подробности см. в документации атрибутаis_active attribute on the built-in user model.
-
get_full_name() -
Необязательный метод. Более полное официальное обозначение пользователя, например его полное имя. Если метод реализован, это обозначение отображается рядом с именем пользователя в истории объекта в
django.contrib.admin.
-
get_short_name() -
Необязательный метод. Краткое неофициальное обозначение пользователя, например его имя. Если метод реализован, оно заменяет имя пользователя в приветствии в заголовке
django.contrib.admin.
Импорт
AbstractBaseUserAbstractBaseUserиBaseUserManagerможно импортировать изdjango.contrib.auth.base_user, не добавляяdjango.contrib.authвINSTALLED_APPS. -
Следующие атрибуты и методы доступны в любом подклассе AbstractBaseUser:
-
class models.AbstractBaseUser -
-
get_username() -
Возвращает значение поля, указанного в
USERNAME_FIELD.
-
clean() -
Нормализует имя пользователя, вызывая
normalize_username(). Если вы переопределяете этот метод, обязательно вызовитеsuper(), чтобы сохранить нормализацию.
-
classmethod get_email_field_name() -
Возвращает имя поля электронной почты, указанное в атрибуте
EMAIL_FIELD. По умолчанию возвращает'email', еслиEMAIL_FIELDне указан.
-
classmethod normalize_username(username) -
Применяет к именам пользователей нормализацию Unicode NFKC, чтобы визуально одинаковые символы с разными кодовыми точками Unicode считались одинаковыми.
-
is_authenticated -
Атрибут только для чтения, значение которого всегда равно
True(в отличие отAnonymousUser.is_authenticated, значение которого всегда равноFalse). Он позволяет определить, прошёл ли пользователь аутентификацию. Это не означает наличия каких-либо разрешений и не проверяет, активен ли пользователь и есть ли у него действительный сеанс. Обычно этот атрибут проверяют уrequest.user, чтобы выяснить, был ли объект заполнен промежуточным слоемAuthenticationMiddleware(представляющим вошедшего в систему пользователя), однако следует учитывать, что для любого экземпляраUserэтот атрибут имеет значениеTrue.
-
is_anonymous -
Атрибут только для чтения, значение которого всегда равно
False. Он позволяет отличать объектыUserот объектовAnonymousUser. Как правило, вместо этого атрибута рекомендуется использоватьis_authenticated.
-
set_password(raw_password) -
Устанавливает пользователю пароль из заданной строки в открытом виде, выполняя необходимое хеширование. Объект
AbstractBaseUserне сохраняется.Если raw_password имеет значение
None, будет установлен непригодный для использования пароль, как если бы был вызван методset_unusable_password().
-
check_password(raw_password)
-
acheck_password(raw_password) -
Асинхронная версия:
acheck_password()Возвращает
True, если заданная строка в открытом виде является правильным паролем пользователя. (При сравнении выполняется необходимое хеширование пароля.)
-
set_unusable_password() -
Отмечает, что у пользователя не установлен пароль. Это не то же самое, что пустая строка в качестве пароля. Метод
check_password()для этого пользователя никогда не вернётTrue. ОбъектAbstractBaseUserне сохраняется.Этот метод может понадобиться, если аутентификация приложения выполняется через существующий внешний источник, например каталог LDAP.
-
has_usable_password() -
Возвращает
False, если для этого пользователя был вызван методset_unusable_password().
-
get_session_auth_hash() -
Возвращает HMAC поля пароля. Используется для инвалидации сеанса при смене пароля.
-
get_session_auth_fallback_hash() -
Возвращает HMAC поля пароля, используя
SECRET_KEY_FALLBACKS. Используется методомget_user().
-
AbstractUser является подклассом AbstractBaseUser:
-
class models.AbstractUser -
-
clean() -
Нормализует адрес электронной почты, вызывая
BaseUserManager.normalize_email(). Если вы переопределяете этот метод, обязательно вызовитеsuper(), чтобы сохранить нормализацию.
-
Создание менеджера для пользовательской модели
Для пользовательской модели также следует определить пользовательский менеджер. Если ваша пользовательская модель содержит поля username, email, is_staff, is_active, is_superuser, last_login и date_joined, совпадающие с полями пользователя Django по умолчанию, можно установить менеджер Django UserManager. Если же ваша пользовательская модель содержит другие поля, потребуется определить пользовательский менеджер, унаследованный от BaseUserManager, с двумя дополнительными методами:
-
class models.CustomUserManager -
-
create_user(username_field, password=None, **other_fields) -
В сигнатуре
create_user()должны присутствовать поле имени пользователя и все обязательные поля в качестве аргументов. Например, если в пользовательской модели в качестве поля имени пользователя используетсяemail, аdate_of_birthявляется обязательным полем, методcreate_userследует определить так:def create_user(self, email, date_of_birth, password=None): # create user here ...
-
create_superuser(username_field, password=None, **other_fields) -
В сигнатуре
create_superuser()должны присутствовать поле имени пользователя и все обязательные поля в качестве аргументов. Например, если в пользовательской модели в качестве поля имени пользователя используетсяemail, аdate_of_birthявляется обязательным полем, методcreate_superuserследует определить так:def create_superuser(self, email, date_of_birth, password=None): # create superuser here ...
-
Если ForeignKey используется в USERNAME_FIELD или REQUIRED_FIELDS, эти методы получают значение to_field (по умолчанию — primary_key) существующего экземпляра.
BaseUserManager предоставляет следующие вспомогательные методы:
-
class models.BaseUserManager -
-
classmethod normalize_email(email) -
Нормализует адреса электронной почты, преобразуя доменную часть адреса в нижний регистр.
-
get_by_natural_key(username)
-
aget_by_natural_key(username) -
Асинхронная версия:
aget_by_natural_key()Получает экземпляр пользователя, используя содержимое поля, указанного в
USERNAME_FIELD.Изменено в Django 5.2:Добавлен метод
aget_by_natural_key().
-
Расширение стандартной модели пользователя Django User
Если вас полностью устраивает модель Django User, но вы хотите добавить дополнительные сведения о профиле, можно унаследовать django.contrib.auth.models.AbstractUser и добавить собственные поля профиля, хотя мы рекомендуем использовать отдельную модель, как описано в разделе Выбор пользовательской модели. AbstractUser содержит полную реализацию стандартной модели User в виде абстрактной модели.
Пользовательские модели и встроенные формы аутентификации
Встроенные в Django формы и представления делают определённые предположения об используемой пользовательской модели.
Следующие формы совместимы с любым подклассом AbstractBaseUser:
-
AuthenticationForm: использует поле имени пользователя, указанное вUSERNAME_FIELD. SetPasswordFormPasswordChangeFormAdminPasswordChangeForm
Следующие формы делают определённые предположения о пользовательской модели и могут использоваться без изменений, если эти предположения выполняются:
-
PasswordResetForm: предполагает, что в пользовательской модели есть поле для адреса электронной почты пользователя с именем, возвращаемым методомget_email_field_name()(по умолчанию —email), которое можно использовать для идентификации пользователя, а также логическое поле с именемis_active, запрещающее сброс пароля неактивными пользователями.
Наконец, следующие формы связаны с User, поэтому для работы с пользовательской моделью их необходимо переписать или расширить:
Если ваша пользовательская модель является подклассом AbstractUser, можно расширить эти формы следующим образом:
from django.contrib.auth.forms import UserCreationForm
from myapp.models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = UserCreationForm.Meta.fields + ("custom_field",)
Пользовательские модели пользователей и django.contrib.admin
Если вы хотите, чтобы ваша пользовательская модель пользователя также работала с административным сайтом, она должна определять некоторые дополнительные атрибуты и методы. Эти методы позволяют административному сайту управлять доступом пользователя к его содержимому:
- classmodels.CustomUser
-
is_staff -
Возвращает
True, если пользователю разрешен доступ к административному сайту.
-
is_active -
Возвращает
True, если учетная запись пользователя активна в данный момент.
- has_perm(perm, obj=None):
-
Возвращает
True, если у пользователя есть указанное разрешение. Если заданobj, разрешение необходимо проверить для определенного экземпляра объекта.
- has_module_perms(app_label):
-
Возвращает
True, если у пользователя есть разрешение на доступ к моделям указанного приложения.
Также необходимо зарегистрировать пользовательскую модель пользователя в административном сайте. Если ваша пользовательская модель пользователя наследуется от django.contrib.auth.models.AbstractUser, можно использовать существующий класс Django django.contrib.auth.admin.UserAdmin. Однако если ваша модель пользователя наследуется от AbstractBaseUser, потребуется определить пользовательский класс ModelAdmin. Возможно, удастся унаследоваться от стандартного django.contrib.auth.admin.UserAdmin; однако потребуется переопределить все определения, которые ссылаются на поля в django.contrib.auth.models.AbstractUser, отсутствующие в вашем пользовательском классе пользователя.
Примечание
Если вы используете пользовательский ModelAdmin, являющийся подклассом django.contrib.auth.admin.UserAdmin, необходимо добавить пользовательские поля в fieldsets (для полей, используемых при редактировании пользователей) и в add_fieldsets (для полей, используемых при создании пользователя). Например:
from django.contrib.auth.admin import UserAdmin
class CustomUserAdmin(UserAdmin):
...
fieldsets = UserAdmin.fieldsets + ((None, {"fields": ["custom_field"]}),)
add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ["custom_field"]}),)
Подробнее см. полный пример.
Пользовательские модели пользователей и разрешения
Чтобы упростить включение системы разрешений Django в собственный класс пользователя, Django предоставляет PermissionsMixin. Это абстрактная модель, которую можно включить в иерархию классов модели пользователя. Она предоставляет все методы и поля базы данных, необходимые для поддержки системы разрешений Django.
PermissionsMixin предоставляет следующие методы и атрибуты:
-
class models.PermissionsMixin -
-
is_superuser -
Логическое значение. Указывает, что у пользователя есть все разрешения без необходимости назначать их явно.
-
get_user_permissions(obj=None) -
Возвращает набор строк разрешений, непосредственно имеющихся у пользователя.
Если передан
obj, возвращаются только разрешения пользователя для этого конкретного объекта.
-
get_group_permissions(obj=None) -
Возвращает набор строк разрешений, имеющихся у пользователя благодаря его группам.
Если передан
obj, возвращаются только разрешения групп для этого конкретного объекта.
-
get_all_permissions(obj=None) -
Возвращает набор строк разрешений, имеющихся у пользователя благодаря группам и личным разрешениям.
Если передан
obj, возвращаются только разрешения для этого конкретного объекта.
-
has_perm(perm, obj=None) -
Возвращает
True, если у пользователя есть указанное разрешение, гдеpermимеет формат"<app label>.<permission codename>"(см. разрешения). ЕслиUser.is_activeиis_superuserодновременно равныTrue, этот метод всегда возвращаетTrue.Если передан
obj, этот метод проверяет разрешение не для модели, а для конкретного объекта.
-
has_perms(perm_list, obj=None) -
Возвращает
True, если у пользователя есть каждое из указанных разрешений, где каждое разрешение имеет формат"<app label>.<permission codename>". ЕслиUser.is_activeиis_superuserодновременно равныTrue, этот метод всегда возвращаетTrue.Если передан
obj, этот метод проверяет разрешения не для модели, а для конкретного объекта.
-
has_module_perms(package_name) -
Возвращает
True, если у пользователя есть какие-либо разрешения в указанном пакете (метке приложения Django). ЕслиUser.is_activeиis_superuserодновременно равныTrue, этот метод всегда возвращаетTrue.
-
Пользовательские модели пользователей и прокси-модели
Одно из ограничений пользовательских моделей пользователей заключается в том, что установка такой модели нарушает работу всех прокси-моделей, наследуемых от User. Прокси-модели должны основываться на конкретном базовом классе; определяя пользовательскую модель пользователя, вы лишаете Django возможности надежно определить базовый класс.
Если в проекте используются прокси-модели, необходимо либо изменить прокси-модель, чтобы она наследовалась от модели пользователя, используемой в проекте, либо объединить поведение прокси-модели с подклассом User.
Полный пример
Вот пример приложения с пользовательской моделью пользователя, совместимой с административным сайтом. В этой модели в качестве имени пользователя используется адрес электронной почты, а дата рождения является обязательной; проверка разрешений не реализована, за исключением флага admin в учетной записи пользователя. Эта модель совместима со всеми встроенными формами и представлениями аутентификации, за исключением форм создания пользователей. Пример показывает, как взаимодействуют большинство компонентов, но его не следует напрямую копировать в проекты для использования в рабочей среде.
Весь этот код будет находиться в файле models.py пользовательского приложения аутентификации:
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
class MyUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError("Users must have an email address")
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, date_of_birth, password=None):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
email,
password=password,
date_of_birth=date_of_birth,
)
user.is_admin = True
user.save(using=self._db)
return user
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name="email address",
max_length=255,
unique=True,
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = MyUserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["date_of_birth"]
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
@property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
Затем, чтобы зарегистрировать эту пользовательскую модель пользователя в административном сайте Django, в файл admin.py приложения нужно добавить следующий код:
from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError
from customauth.models import MyUser
class UserCreationForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
password2 = forms.CharField(
label="Password confirmation", widget=forms.PasswordInput
)
class Meta:
model = MyUser
fields = ["email", "date_of_birth"]
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
disabled password hash display field.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = MyUser
fields = ["email", "password", "date_of_birth", "is_active", "is_admin"]
class UserAdmin(BaseUserAdmin):
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ["email", "date_of_birth", "is_admin"]
list_filter = ["is_admin"]
fieldsets = [
(None, {"fields": ["email", "password"]}),
("Personal info", {"fields": ["date_of_birth"]}),
("Permissions", {"fields": ["is_admin"]}),
]
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = [
(
None,
{
"classes": ["wide"],
"fields": ["email", "date_of_birth", "password1", "password2"],
},
),
]
search_fields = ["email"]
ordering = ["email"]
filter_horizontal = []
# Now register the new UserAdmin...
admin.site.register(MyUser, UserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)
Наконец, укажите пользовательскую модель в качестве модели пользователя по умолчанию для проекта с помощью параметра AUTH_USER_MODEL в файле settings.py:
AUTH_USER_MODEL = "customauth.MyUser"
Добавление асинхронного интерфейса
Чтобы оптимизировать производительность при вызове из асинхронного контекста аутентификации, бэкенды могут реализовать асинхронные версии каждой функции — aget_user(user_id) и aauthenticate(request, **credentials). Если бэкенд аутентификации наследуется от BaseBackend и асинхронные версии этих функций не предоставлены, они будут автоматически созданы с помощью sync_to_async. Это снижает производительность.
Хотя асинхронный интерфейс необязателен, синхронный интерфейс всегда необходим. Если реализован асинхронный интерфейс, синхронный интерфейс автоматически не создается.
Встроенные бэкенды аутентификации Django изначально поддерживают асинхронную работу. При наследовании от этих бэкендов особенно важно также изменять асинхронные версии измененных функций.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/topics/auth/customizing/