Привет, Хабр!
Аутентификация является фундаментальной частью любого веб-приложения. Мы рассмотрим различные способы реализации аутентификации в Django, начиная от стандартных методов и заканчивая более крутыми техниками, например как 2FA и OAuth2.
Стандартная аутентификация Django
Начнём с классики. Django поставляется со встроенной системой аутентификации, которая покрывает большинство базовых потребностей. Она включает в себя модели, формы, представления и шаблоны для работы с пользователями.
Первым делом убедимся, что в нашем проекте подключены все необходимые приложения и middleware для работы системы аутентификации.
settings.py
INSTALLED_APPS = [
# ...
'django.contrib.auth', # Приложение аутентификации
'django.contrib.contenttypes',# Контент-тайпы для моделей
# ...
]
MIDDLEWARE = [
# ...
'django.contrib.sessions.middleware.SessionMiddleware', # Управление сессиями
'django.contrib.auth.middleware.AuthenticationMiddleware',# Аутентификация
# ...
]
Эти настройки необходимы для корректной работы системы аутентификации. Middleware SessionMiddleware
и AuthenticationMiddleware
позволяют управлять сессиями и обработку аутентификации на уровне запросов.
Создание пользовательских форм
Django имеет готовые формы для регистрации UserCreationForm
и входа AuthenticationForm
, но часто возникает необходимость добавить дополнительные поля или изменить логику валидации. В таких случаях можно создать свои формы, наследуясь от стандартных.
forms.py
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.models import User
from django import forms
class SignUpForm(UserCreationForm):
email = forms.EmailField(max_length=254, help_text='Обязательное поле. Введите действующий email.')
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2')
class LoginForm(AuthenticationForm):
username = forms.CharField(label='Имя пользователя')
password = forms.CharField(label='Пароль', widget=forms.PasswordInput)
В SignUpForm
мы добавили поле email
, сделав его обязательным. В Meta
классе указали, какие поля должны отображаться в форме. LoginForm
переопределяет стандартную форму входа, позволяя нам изменять метки полей и виджеты.
Создание представлений для регистрации и входа
Теперь создадим представления, которые будут обрабатывать запросы на регистрацию и вход пользователей.
views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate
from .forms import SignUpForm, LoginForm
def signup_view(request):
if request.method == 'POST':
form = SignUpForm(request.POST)
if form.is_valid():
user = form.save() # Сохраняем нового пользователя
login(request, user) # Выполняем вход
return redirect('home') # Перенаправляем на главную страницу
else:
form = SignUpForm()
return render(request, 'signup.html', {'form': form})
def login_view(request):
form = LoginForm(data=request.POST or None)
if request.method == 'POST':
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user = authenticate(username=username, password=password) # Проверяем учетные данные
if user is not None:
login(request, user) # Выполняем вход
return redirect('home') # Перенаправляем на главную страницу
return render(request, 'login.html', {'form': form})
В signup_view
мы обрабатываем POST-запросы с данными формы регистрации. Если данные валидны, сохраняем пользователя и выполняем вход с помощью функции login
. В login_view
обрабатываем форму входа, аутентифицируем пользователя с помощью функции authenticate
и, если он существует, выполняем вход.
Настройка URL-маршрутов
Не забудем настроить URL-маршруты для наших представлений.
urls.py
from django.urls import path
from .views import signup_view, login_view
urlpatterns = [
path('signup/', signup_view, name='signup'),
path('login/', login_view, name='login'),
# ...
]
Теперь страницы регистрации и входа доступны по URL-адресам /signup/
и /login/
соответственно.
Кастомизация модели пользователя
Стандартная модель пользователя Django User
может быть недостаточной для некоторых проектов, особенно если требуется хранить дополнительные данные о пользователях. В таких случаях рекомендуется создать свою пользовательскую модель, наследуясь от AbstractUser
или AbstractBaseUser
.
Создание пользовательской модели
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
# Добавляем дополнительные поля
phone_number = models.CharField(max_length=15, blank=True, null=True)
def __str__(self):
return self.username
Здесь создаём класс CustomUser
, наследующийся от AbstractUser
, и добавляем новое поле phone_number
для хранения номера телефона пользователя.
Настройка проекта для использования новой модели
Чтобы Django использовал нашу кастомную модель пользователя, нужно указать её в настройках проекта.
settings.py
AUTH_USER_MODEL = 'your_app_name.CustomUser'
После этого нужно создать и применить миграции для новой модели:
python manage.py makemigrations
python manage.py migrate
Обновление форм и представлений
Так как мы изменили модель пользователя, нужно обновить формы и представления, чтобы они работали с новой моделью.
forms.py
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from .models import CustomUser
from django import forms
class SignUpForm(UserCreationForm):
email = forms.EmailField(max_length=254, help_text='Обязательное поле. Введите действующий email.')
phone_number = forms.CharField(max_length=15, required=False, help_text='Необязательное поле.')
class Meta:
model = CustomUser
fields = ('username', 'email', 'phone_number', 'password1', 'password2')
Мы заменили модель User
на CustomUser
и добавили поле phone_number
в форму регистрации.
Использование токенов для аутентификации
Если вы разрабатываете API, то токен-авторизация — лучший выбор для обеспечения безопасности и управления доступом. Она позволяет клиентским приложениям аутентифицироваться без использования сессий.
Установим Django REST Framework
Для реализации токен-авторизации установим Django REST Framework и соответствующий пакет для токенов.
pip install djangorestframework djangorestframework.authtoken
Настроим проект
Добавим необходимые приложения в настройки проекта.
settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'rest_framework.authtoken',
# ...
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication', # Аутентификация по токену
],
}
Генерация токена для пользователя
Чтобы пользователь мог аутентифицироваться по токену, необходимо сгенерировать для него токен.
Пример генерации токена
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.get(username='example_user')
token, created = Token.objects.get_or_create(user=user)
print(token.key)
Этот код можно выполнить в консоли Django python manage.py shell
или включить в скрипт. Он получает пользователя с именем example_user
и создаёт или получает токен, связанный с этим пользователем.
Использование токена в запросах
При отправке запросов к API необходимо добавлять токен в заголовок HTTP-запроса:
Authorization: Token your_token_key
Где your_token_key
— это значение токена, сгенерированного ранее.
Настройка эндпоинта для получения токена
Чтобы пользователи могли получать токены, можно настроить специальный эндпоинт.
urls.py
from django.urls import path
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
# ...
path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
# ...
]
Теперь пользователь может получить токен, отправив POST-запрос с username
и password
на /api-token-auth/
.
OAuth2 аутентификация с django-allauth
OAuth2 позволяет пользователям входить в приложение через аккаунты в социальных сетях, например через тот же Google.
Установим пакет django-allauth
, который предоставляет готовые решения для аутентификации через социальные сети.
pip install django-allauth
Настройка проекта
Добавим необходимые приложения и настройки.
settings.py
INSTALLED_APPS = [
# ...
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
# Добавляем провайдеров социальных сетей
'allauth.socialaccount.providers.google',
# ...
]
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
SITE_ID = 1
# Дополнительные настройки allauth
ACCOUNT_EMAIL_VERIFICATION = 'none' # Не требовать верификацию email
ACCOUNT_AUTHENTICATION_METHOD = 'username'# Метод аутентификации
ACCOUNT_EMAIL_REQUIRED = True # Требовать email
Настройка URL-маршрутов
Добавим URL-адреса для обработки запросов allauth.
urls.py
from django.urls import path, include
urlpatterns = [
# ...
path('accounts/', include('allauth.urls')),
# ...
]
После миграций и создания суперпользователя, можно настроить приложения социальных сетей в админке Django. Пеереходим в раздел Social applications и добавляем новое приложение, указав необходимые параметры, такие как Client ID
и Client Secret
, которые можно получить в соответствующих консольных разработчиков.
Двухфакторная аутентификация
Для повышения безопасности вашего Django-приложения можно добавить двухфакторную аутентификацию, которая требует дополнительного подтверждения при входе в аккаунт.
Установим пакет django-two-factor-auth
, который предоставляет готовое решение для реализации 2FA в Django.
pip install django-two-factor-auth
Добавим необходимые приложения и middleware в настройки вашего проекта.
settings.py
INSTALLED_APPS = [
# ...
'django_otp', # Основное приложение для работы с OTP
'django_otp.plugins.otp_static', # Поддержка статических кодов
'django_otp.plugins.otp_totp', # Поддержка TOTP (Time-based One-Time Password)
'two_factor', # Приложение двухфакторной аутентификации
'crispy_forms', # Для красивых форм
'crispy_bootstrap5', # Для использования Bootstrap 5
# ...
]
MIDDLEWARE = [
# ...
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_otp.middleware.OTPMiddleware', # Middleware для обработки OTP
# ...
]
# Настройки для crispy_forms
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
CRISPY_TEMPLATE_PACK = 'bootstrap5'
# Настройки для шаблонов
TEMPLATES = [
{
# ...
'OPTIONS': {
'context_processors': [
# ...
'django.template.context_processors.request', # Необходимо для двухфакторной аутентификации
# ...
],
},
},
]
Убедитесь, что django.template.context_processors.request
добавлен в context_processors
. Это необходимо для корректной работы шаблонов django-two-factor-auth
.
Добавим маршруты для двухфакторной аутентификации в ваш файл urls.py
.
urls.py
from django.contrib import admin
from django.urls import path, include
from two_factor.urls import urlpatterns as tf_urls
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(tf_urls)), # Маршруты для двухфакторной аутентификации
# ...
]
Пакет django-two-factor-auth
использует шаблоны для отображения форм и сообщений пользователю. По умолчанию он использует стандартные шаблоны Django. Если нужно использовать Bootstrap для оформления форм, нужно проверить, что в проекте настроены соответствующие шаблоны и статические файлы.
Установим Bootstrap и настроим статику:
Установим Bootstrap через npm или используем CDN.
npm install bootstrap
Или подключите Bootstrap через CDN в базовом шаблоне:
templates/base.html
<!DOCTYPE html>
<html lang="ru">
<head>
<!-- ... -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<!-- ... -->
</head>
<body>
<!-- ... -->
{% block content %}{% endblock %}
<!-- ... -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Настроим статические файлы
settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / "static",
]
Для подтверждения входа с помощью одноразовых кодов пользователю может быть отправлено сообщение на email или SMS. Рассмотрим отправку на email.
Настройка email в settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.your-email-provider.com'
EMAIL_PORT = 587 # или 465 для SSL
EMAIL_USE_TLS = True # или EMAIL_USE_SSL = True, если используете SSL
EMAIL_HOST_USER = 'your-email@example.com'
EMAIL_HOST_PASSWORD = 'your-email-password'
DEFAULT_FROM_EMAIL = 'Your Project Name <your-email@example.com>'
По умолчанию django-two-factor-auth
поддерживает несколько методов доставки токенов:
Генерация кода в приложении-аутентификаторе, таком как Google Authenticator или Authy.
Отправка одноразовых паролей по SMS или электронной почте.
Если хочется отправлять коды по электронной почте, убедитесь, что в settings.py
настроена соответствующая доставка.
settings.py
TWO_FACTOR_EMAIL_GATEWAY = 'two_factor.gateways.email.EmailDevice'
Если вы планируете использовать SMS, нужно настроить SMS-шлюз, например, с помощью Twilio.
Если вы используете кастомную модель пользователя, убедитесь, что она совместима с django-two-factor-auth
. Обычно достаточно того, что модель наследуется от AbstractUser
или AbstractBaseUser
.
Переопределим представления входа, чтобы включить двухфакторную аутентификацию.
urls.py
from django.urls import path, include
from two_factor.urls import urlpatterns as tf_urls
from django.contrib.auth import views as auth_views
urlpatterns = [
# ...
path('', include(tf_urls)), # Маршруты для двухфакторной аутентификации
# ...
]
Теперь при переходе на /account/login/
будет использоваться представление входа с поддержкой двухфакторной аутентификации.
Можно настроить, куда перенаправлять пользователя после успешного входа или выхода.
settings.py
LOGIN_REDIRECT_URL = '/dashboard/' # После успешного входа
LOGOUT_REDIRECT_URL = '/account/login/' # После выхода
Пакет django-two-factor-auth
поставляется с базовыми шаблонами, но можно переопределить их, создав свои собственные в каталоге templates/
.
Например, чтобы изменить шаблон ввода одноразового пароля, создайте файл templates/two_factor/core/otp.html
и добавьте в него код.
templates/two_factor/core/otp.html
{% extends "base.html" %}
{% block content %}
<h2>Ввод одноразового пароля</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Подтвердить</button>
</form>
{% endblock %}
Если вы используете административный интерфейс Django, можно защитить его с помощью двухфакторной аутентификации.
admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth import get_user_model
from two_factor.admin import AdminSiteOTPRequired, AdminSiteOTPRequiredMixin
User = get_user_model()
class OTPAdminSite(AdminSiteOTPRequired):
pass
admin_site = OTPAdminSite(name='OTPAdmin')
@admin.register(User, site=admin_site)
class CustomUserAdmin(UserAdmin):
pass
Дополнительные настройки
Можно настроить время жизни токенов восстановления или одноразовых паролей.
settings.py
TWO_FACTOR_REMEMBER_COOKIE_AGE = 1209600 # 14 дней в секундах
Если хочется ограничить методы доставки токенов, можно настроить это следующим образом:
TWO_FACTOR_CALL_GATEWAY = 'your_project.gateways.custom_call_gateway.CustomCallGateway'
TWO_FACTOR_SMS_GATEWAY = 'your_project.gateways.custom_sms_gateway.CustomSmsGateway'
Magic Link для входа
Magic Link — это метод аутентификации без пароля, при котором пользователю отправляется специальная одноразовая ссылка на его электронную почту для входа в систему.
Для реализации Magic Link в Django воспользуемся пакетом django-magiclink
.
pip install django-magiclink
Добавим приложение magiclink
в список установленных приложений и настроим бэкенды аутентификации.
settings.py
INSTALLED_APPS = [
# ...
'magiclink',
# ...
]
AUTHENTICATION_BACKENDS = [
'magiclink.backends.MagicLinkBackend', # Бэкенд Magic Link
'django.contrib.auth.backends.ModelBackend', # Стандартный бэкенд
]
Чтобы Magic Link мог отправлять письма со ссылками для входа, необходимо настроить отправку электронной почты в вашем проекте. Добавляем следующие настройки в settings.py
:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.your-email-provider.com'
EMAIL_PORT = 587 # или 465 для SSL
EMAIL_USE_TLS = True # или EMAIL_USE_SSL = True, если используете SSL
EMAIL_HOST_USER = 'your-email@example.com'
EMAIL_HOST_PASSWORD = 'your-email-password'
DEFAULT_FROM_EMAIL = 'Your Project Name '
Подключим представления из пакета django-magiclink
.
urls.py
from django.urls import path, include
from magiclink import urls as magiclink_urls
urlpatterns = [
# ...
path('accounts/', include(magiclink_urls)),
# ...
]
Пакет django-magiclink
дает набор готовых URL-маршрутов и представлений для обработки запросов на отправку и активацию Magic Link.
По дефолтуdjango-magiclink
использует стандартные шаблоны для писем. Можно настроить их под свой дизайн, создав соответствующие шаблоны в вашем приложении.
Создаем файл templates/magiclink/email/email_subject.txt
с темой письма:
Ссылка для входа на сайт {{ site_name }}
И файл templates/magiclink/email/email_body.txt
с содержимым письма:
Привет,
Вы запросили ссылку для входа на сайт {{ site_name }}. Перейдите по ссылке ниже, чтобы войти:
{{ magic_link }}
Если вы не запрашивали ссылку, просто проигнорируйте это письмо.
Спасибо,
Команда {{ site_name }}
В settings.py
можно настроить различные параметры работы django-magiclink
.
MAGICLINK = {
'LINK_EXPIRATION': 3600, # Время жизни ссылки в секундах (по умолчанию 3600 секунд = 1 час)
'REDIRECT_URL': '/', # URL для перенаправления после успешного входа
'EMAIL_SUBJECT_TEMPLATE': 'magiclink/email/email_subject.txt',
'EMAIL_BODY_TEMPLATE': 'magiclink/email/email_body.txt',
'SUCCESS_MESSAGE': 'Проверьте вашу электронную почту для входа в систему.',
}
Создайте шаблон для формы ввода email, чтобы пользователь мог запросить Magic Link.
templates/magiclink/login.html
{% extends "base.html" %}
{% block content %}
<h2>Вход по Magic Link</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Отправить ссылку для входа</button>
</form>
{% endblock %}
Помимо этого, должен быть базовый шаблон base.html
, от которого наследуются другие шаблоны.
templates/base.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{% block title %}Мой сайт{% endblock %}</title>
</head>
<body>
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% block content %}{% endblock %}
</body>
</html>
Если хочется изменить поведение представлений, можно создать свои собственные, наследуясь от представлений django-magiclink
.
views.py
from magiclink.views import MagicLinkLoginView, MagicLinkCompleteView
class CustomMagicLinkLoginView(MagicLinkLoginView):
template_name = 'magiclink/login.html'
class CustomMagicLinkCompleteView(MagicLinkCompleteView):
success_url = '/dashboard/' # Перенаправление после успешного входа
urls.py
from django.urls import path
from .views import CustomMagicLinkLoginView, CustomMagicLinkCompleteView
urlpatterns = [
# ...
path('accounts/login/', CustomMagicLinkLoginView.as_view(), name='magiclink_login'),
path('accounts/login/complete/', CustomMagicLinkCompleteView.as_view(), name='magiclink_complete'),
# ...
]
По умолчанию django-magiclink
может создавать новых пользователей при первом использовании Magic Link. Если нужно отключить автоматическое создание пользователей, добавьте следующую настройку:
settings.py
MAGICLINK = {
# ...
'CREATE_USER': False,
}
В этом случае, если пользователь с указанным email не существует, ссылка не будет отправлена.
Спасибо, что дочитали до конца! Если у вас остались вопросы или вы хотите поделиться своим опытом, пишите в комментариях.
А сейчас приглашаю вас на бесплатные вебинары курса Python Developer. Professional:
7.11. gRPC в Python. Записаться.
13.11. Делаем по красоте: паттерны проектирования в Python-приложениях. Записаться.
21.11. Основы FastAPI. Записаться.
sportp
Спасибо
Перехожу с версии 1.4 на 4.2. И как раз сегодня в плане почистить всю auth.