Django-аутентификация: просто о сложном

от автора

Привет, Хабр!

Аутентификация является фундаментальной частью любого веб-приложения. Мы рассмотрим различные способы реализации аутентификации в 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:


ссылка на оригинал статьи https://habr.com/ru/articles/855086/