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

343ec39bcb17128f846958d30be7bfa5.jpg

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

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




    
    
    


    
    {% block content %}{% endblock %}
    
    












Настроим статические файлы

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 '

По умолчанию 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 %}
  

Ввод одноразового пароля

{% csrf_token %} {{ form.as_p }}
{% 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 %}
  

Вход по Magic Link

{% csrf_token %} {{ form.as_p }}
{% endblock %}

Помимо этого, должен быть базовый шаблон base.html, от которого наследуются другие шаблоны.

templates/base.html




    
    {% block title %}Мой сайт{% endblock %}


    {% if messages %}
        
    {% for message in messages %}
  • {{ message }}
  • {% endfor %}
{% endif %} {% block content %}{% endblock %}

Если хочется изменить поведение представлений, можно создать свои собственные, наследуясь от представлений 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:

© Habrahabr.ru