Сессии и cookie
Проверка пароля отвечает только на один текущий запрос. Для чтения следующей страницы приложение должно узнать, что вход уже состоялся. Эту связь называют сессией. В нашем Flask-примере используем подписанную cookie, а состояние пользователя дополнительно сверяем с базой.
Результат урока — вход с короткой сессией и явными параметрами cookie. Подпись защищает целостность данных; она не превращает содержимое в секретное хранилище. Поэтому в cookie остаются только ID, версия доступа и служебные значения, необходимые приложению.
Что подтверждает подпись
Браузер хранит значение и отправляет его при следующем запросе. Flask проверяет подпись с серверным SECRET_KEY. Если ID внутри значения был изменён без корректной подписи, приложение не должно принимать такую сессию.
При этом пользователь может видеть сериализованное содержимое своей cookie. Пароль, ключ базы и личный текст туда не помещаются. Даже когда интерфейс не показывает значения напрямую, подпись не является шифрованием.
Ключ должен быть случайным и находиться вне исходника. Для отдельного стенда читатель генерирует его самостоятельно и задаёт LAB_SECRET в окружении. Финальное приложение прекращает запуск, если значение отсутствует или явно слишком короткое. Проверка длины помогает заметить ошибку конфигурации, но не доказывает случайность.
Настройка cookie
Ниже параметры заключительного приложения. Переменная https_mode происходит из конфигурации окружения, а не из пользовательского запроса.
from datetime import timedelta
app.config.update(
SECRET_KEY=secret,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax",
SESSION_COOKIE_SECURE=https_mode,
SESSION_REFRESH_EACH_REQUEST=False,
PERMANENT_SESSION_LIFETIME=timedelta(minutes=30),
)
HttpOnly ограничивает чтение cookie обычным JavaScript страницы. Он не предотвращает все последствия XSS: выполняющаяся на странице программа может обращаться к разрешённым маршрутам. Поэтому правильный вывод текста остаётся необходимым.
Secure ограничивает отправку cookie HTTPS-транспортом. На отдельном локальном HTTP-стенде https_mode выключен, иначе выбранный способ практики не соответствует настройке. Для настоящего HTTPS он включается явно; мы вернёмся к связи с прокси в уроке 19.
SameSite Lax влияет на отправку cookie между сайтами. Это полезное ограничение, но не полноценная проверка подлинности каждого изменения. В уроке 10 будет отдельный токен CSRF. Назначение параметров описано в документации Flask по безопасности.
Создание сессии после входа
После успешного verify_credentials очистим прежнее состояние и сохраним новую личность. Ниже фрагмент успешной ветки обработчика login:
import secrets
from flask import session
session.clear()
session["user_id"] = user["id"]
session["auth_version"] = user["auth_version"]
session["csrf_token"] = secrets.token_urlsafe(32)
session.permanent = True
Пользователь получен из базы и проверки пароля, а не из скрытого поля формы. Очистка устраняет случайные значения прежнего состояния. CSRF-токен здесь создаётся заранее; его проверка будет введена отдельным уроком.
Признак permanent связывает сессию с настроенным временем жизни. Мы отключаем автоматическое обновление при каждом неизменяющем обращении. Другие операции, меняющие саму сессию, могут сформировать новую cookie; срок нужно понимать по фактическому механизму, а не только по подписи интерфейса «30 минут».
Не сохраняйте в сессии роль, если сервер может её изменить и затем больше никогда не сверяет базу. Такой снимок полномочий способен устареть. Для небольшой библиотеки удобнее получать действующего пользователя при каждом обращении.
Проверка серверного состояния
Перед маршрутом приложение читает ID из проверенной Flask-сессии и выбирает пользователя. Если запись отключена или версия доступа изменилась, прежняя сессия перестаёт давать личность.
g.user = None
user_id = session.get("user_id")
if isinstance(user_id, int):
user = db().execute(
"SELECT * FROM users WHERE id = ? AND active = 1",
(user_id,),
).fetchone()
if user and user["auth_version"] == session.get("auth_version"):
g.user = user
else:
session.clear()
Здесь db является функцией получения соединения SQLite, а g — контекстом текущего запроса Flask. Полная версия обработчика находится в заключительном исходнике. Сведения не должны переноситься в общий изменяемый объект между посетителями.
Такая сверка добавляет серверную возможность отзыва всех прежних сессий одного пользователя. Она не создаёт отдельный реестр устройств и не позволяет выборочно отозвать одну из нескольких cookie. Этот предел будет важен в уроке об инциденте.
Наблюдение и выход
После правильного входа новый запрос должен получить user_id действующего пользователя. После изменения auth_version в базе прежняя cookie не подтверждает вход. Ожидаемый результат относится к проверке серверной записи, а не к тому, исчез ли файл cookie из браузера.
Простое session.clear удаляет состояние текущего клиента, но скопированное старое значение может оставаться пригодным до истечения срока, если серверная версия не изменилась. В нашей финальной учебной операции выхода увеличивается auth_version, поэтому выход завершает все сессии владельца. Интерфейс сообщает об этом прямо.
Срок cookie и срок подписи также нужно различать. Браузер управляет локальным хранением, приложение проверяет криптографическое значение, база — действующий доступ. Нельзя доверять только тому, что кнопка входа исчезла со страницы.
Следующее требование
Подтверждённая личность не означает право на каждую заметку. Сейчас приложение знает, что запрос принадлежит Boris; следующий урок должен сделать так, чтобы он всё равно не получил запись Alice.
Срок и открытая форма
Рассмотрим открытую страницу изменения, которая остаётся в браузере после истечения сессии. Поле и скрытый токен ещё видны, но следующий запрос должен пройти серверную проверку заново. Наличие готовой HTML-формы не продлевает полномочие.
При отзыве auth_version middleware очищает неподходящее состояние. Изменяющий запрос может получить отказ CSRF раньше выбора заметки, потому что прежний токен больше не принадлежит действующей сессии. Чтобы отдельно проверить авторизацию, сначала создайте актуальный вход с известным владельцем.
Перезапуск с новым LAB_SECRET также отменяет доверие к прежним подписанным cookie. Это отличается от обычного перезапуска процесса с сохранённым ключом: во втором случае криптографическое условие остаётся тем же. В отчёте полезно указать, какое именно состояние менялось.
Не пытайтесь вручную разбирать и менять формат cookie в прикладном коде. Библиотека отвечает за сериализацию, подпись и срок. Приложение определяет допустимые значения и сверяет серверную версию пользователя.
Если потребуется список устройств с выборочным завершением, понадобится отдельное серверное представление каждой сессии. Auth_version позволяет отозвать все прежние входы владельца, что подходит учебному результату, но не заменяет такой реестр.
Проверьте именно эту границу, а не только успешный вход двух пользователей. Сессия устанавливает контекст для серверного решения. Авторизация применит его к конкретному объекту и действию, сохранив правило при HTML-запросе и API.