Даже в хорошо спроектированном сервисе пользователи регулярно сталкиваются с одной и той же проблемой — забытый пароль. Проект Cinemahub не является исключением: пользователь может долго не заходить на сайт, активно пользоваться каталогом фильмов, а затем внезапно потерять доступ к аккаунту.
В этом уроке мы:
- разберём идею алгоритма восстановления пароля;
- изучим, как Django реализует этот механизм под капотом;
- шаг за шагом подключим стандартные представления Django;
- создадим собственные шаблоны;
- настроим отправку писем;
- проверим весь процесс от формы до входа с новым паролем.
Восстановление пароля — это не просто форма с email. Это цепочка логически связанных шагов, каждый из которых решает свою задачу безопасности.
-
Пользователь нажимает ссылку «Забыли пароль?» на странице входа.
-
Открывается форма, где пользователь вводит email, указанный при регистрации.
-
Django проверяет, существует ли пользователь с таким email.
-
Если пользователь найден:
- создаётся одноразовый токен;
- формируется ссылка с этим токеном;
- ссылка отправляется на email.
-
Пользователь переходит по ссылке из письма.
-
Django проверяет:
- корректность токена;
- срок его действия;
- соответствие пользователю.
-
Пользователь вводит новый пароль.
-
Пароль сохраняется, токен становится недействительным.
-
Пользователь видит сообщение об успешном изменении пароля.
Важно: 👉 На любом этапе пользователь не получает информации о том, существует ли email в системе. Это защита от подбора аккаунтов.
Прежде чем писать код, нужно решить одну практическую задачу: куда будут отправляться письма во время разработки?
На этапе обучения и локальной разработки мы не используем реальный SMTP-сервер. Django предоставляет удобный инструмент — консольный email-бэкенд.
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"Теперь все письма:
- не отправляются в реальный почтовый ящик;
- выводятся прямо в терминал, где запущен сервер.
Перед тем как использовать механизм восстановления пароля, убедимся, что отправка писем работает.
python manage.py shellfrom django.core.mail import send_mail
send_mail(
subject="Cinemahub: восстановление пароля",
message="Тестовое письмо",
from_email="no-reply@cinemahub.local",
recipient_list=["user@example.com"],
)Если настройка выполнена правильно, письмо появится в консоли.
Django уже реализовал весь алгоритм восстановления пароля. Наша задача — правильно подключить и адаптировать его.
Мы будем использовать:
PasswordResetViewPasswordResetDoneViewPasswordResetConfirmViewPasswordResetCompleteView
from django.urls import path
from django.urls import reverse_lazy
from django.contrib.auth.views import (
PasswordResetView,
PasswordResetDoneView,
PasswordResetConfirmView,
PasswordResetCompleteView,
)
app_name = "users"
urlpatterns = [
path(
"password-reset/",
PasswordResetView.as_view(
template_name="users/password_reset_form.html",
email_template_name="users/password_reset_email.html",
success_url=reverse_lazy("users:password_reset_done"),
),
name="password_reset",
),
path(
"password-reset/done/",
PasswordResetDoneView.as_view(
template_name="users/password_reset_done.html"
),
name="password_reset_done",
),
path(
"password-reset/<uidb64>/<token>/",
PasswordResetConfirmView.as_view(
template_name="users/password_reset_confirm.html",
success_url=reverse_lazy("users:password_reset_complete"),
),
name="password_reset_confirm",
),
path(
"password-reset/complete/",
PasswordResetCompleteView.as_view(
template_name="users/password_reset_complete.html"
),
name="password_reset_complete",
),
]{% extends "base.html" %} {% block content %}
<h1>Восстановление пароля</h1>
<p>Укажите email, связанный с аккаунтом Cinemahub.</p>
<form method="post">
{% csrf_token %} {% for field in form %}
<p>
<label for="{{ field.id_for_label }}">{{ field.label }}:</label>
{{ field }}
</p>
<div class="form-error">{{ field.errors }}</div>
{% endfor %}
<button type="submit">Отправить письмо</button>
</form>
{% endblock %}{% extends "base.html" %} {% block content %}
<h1>Письмо отправлено</h1>
<p>
Если аккаунт с таким email существует, инструкции по восстановлению пароля
отправлены на почту.
</p>
<p>Проверьте папку «Спам», если письмо не пришло.</p>
{% endblock %}Вы получили это письмо, потому что был запрошен сброс пароля для аккаунта
Cinemahub. Перейдите по ссылке, чтобы задать новый пароль: {{ protocol }}://{{
domain }}{% url 'users:password_reset_confirm' uidb64=uid token=token %} Если вы
не запрашивали восстановление пароля — просто проигнорируйте это письмо.{% extends "base.html" %} {% block content %}
<h1>Новый пароль</h1>
<form method="post">
{% csrf_token %}
<div class="form-error">{{ form.non_field_errors }}</div>
{% for field in form %}
<p>
<label for="{{ field.id_for_label }}">{{ field.label }}:</label>
{{ field }}
</p>
<div class="form-error">{{ field.errors }}</div>
{% endfor %}
<button type="submit">Сохранить пароль</button>
</form>
{% endblock %}{% extends "base.html" %} {% block content %}
<h1>Пароль изменён</h1>
<p>Пароль вашего аккаунта Cinemahub успешно обновлён.</p>
<p>Теперь вы можете <a href="{% url 'users:login' %}">войти в систему</a>.</p>
{% endblock %}<p>
<a href="{% url 'users:password_reset' %}"> Забыли пароль? </a>
</p>Письмо будет выглядеть примерно так:
0JLRiyDQv9C+0LvRg9GH0LjQu9C4INGN0YLQviDQv9C40YHRjNC80L4sINC/0L7RgtC+0LzRgyDR
h9GC0L4g0LHRi9C7INC30LDQv9GA0L7RiNC10L0g0YHQsdGA0L7RgSDQv9Cw0YDQvtC70Y8K0LTQ
...Тело письма закодировано в base64. Django сделал это автоматически, потому что:
- используется UTF-8;
- письмо потенциально содержит не ASCII-символы (русский текст);
- консольный backend не декодирует письмо, а показывает «сырой» MIME-контент.
В реальном почтовом клиенте (Gmail, Mail.ru и т.п.) это письмо декодировалось бы автоматически и выглядело бы нормально.
Во время обучения и разработки рекомендую делать так:
- Скопировать всё письмо из консоли
- Вставить его в любой base64 decoder
import base64
encoded = """
0JLRiyDQv9C+0LvRg9GH0LjQu9C4INGN0YLQviDQv9C40YHRjNC80L4sINC/0L7RgtC+0LzRgyDR
h9GC0L4g0LHRi9C7INC30LDQv9GA0L7RiNC10L0g0YHQsdGA0L7RgSDQv9Cw0YDQvtC70Y8K0LTQ
...
"""
print(base64.b64decode(encoded).decode("utf-8"))- Открой страницу входа.
- Нажми «Забыли пароль?».
- Введи email пользователя.
- Посмотри письмо в консоли.
- Перейди по ссылке из письма.
- Задай новый пароль.
- Авторизуйся с новым паролем.
Причина:
— забыто пространство имён users: в email-шаблоне.
Причина:
— не настроен EMAIL_BACKEND.
Причина: — неверный или просроченный токен.
Повторить реализацию восстановления пароля.
- Почему для восстановления используется токен?
- Где формируется ссылка для сброса пароля?
- Почему нельзя показывать, существует ли email в системе?
- Чем отличаются
PasswordResetViewиPasswordChangeView? - Где настраивается шаблон письма?
- Зачем нужен
uidb64? - Что произойдёт при повторном использовании токена?
- Почему Django рекомендует встроенный механизм?