Сессия в web-приложении на Python и Starlette

programming

Опубликован:
2024-12-14T00:00:12.118372Z
Отредактирован:
2024-12-14T00:00:12.118372Z
Статус:
публичный
50
0
0

В web-приложении на Python и Starlette, разработку которого мы исследуем в этом цикле статей, уже очень скоро появится собственная система аутентификации пользователей, обеспечивающая самостоятельную регистрацию аккаунта и авторизацию пользователя на основе логина и пароля. Одним из элементов этой системы станет защищённая сессия Starlette — подписанные и зашифрованные cookies, которые, вероятно, можно расшифровать на стороне, но вряд ли возможно подделать. В очередном выпуске этого цикла я займусь подключением сессии в приложение.

Сессия Starlette

Сессия Starlette реализована как middleware класс и обеспечивает заданное поведение на протяжении всего исполнения приложения. Сессия даёт возможность определить в заданном разработчиком месте приложения, обычно в одном из обработчиков запросов, подписанные и зашифрованные cookies, которые впоследствии посылаются серверу с каждым запросом клиента. В документации Starlette утверждается, что данные сессии возможно прочесть, но невозможно модифицировать, и это свойство в процессе разработки мне очень пригодится.

В рамках описываемого в этом цикле статей web-приложения я планирую использовать сессию Starlette в двух вариантах:

  1. Для обмена короткими сообщениями между запросами при переадресациях;

  2. Для идентификации клиента на одном из этапов авторизации пользователя в сервисе.

На текущем этапе разработки, увы, пока невозможно продемонстрировать все приёмы работы с сессией Starlette, поэтому пока я ограничусь простым подключением этого класса middleware в приложение и тестированием полученного результата.

Подключаем сессию в приложение

Сессия Starlette имеет зависимость — itsdangerous, эту библиотеку, для начала, необходимо установить в виртуальное окружение проекта. Запускаю терминал, вхожу в нём в корневой каталог приложения и активирую виртуальное окружение. Установить itsdangerous можно вот такой простой командой.

$ pip install itsdangerous

Поскольку проект имеет собственное Git хранилище, изменения состава виртуального окружения необходимо отразить в нём. Для этого выполняю ещё одну команду, которая перепишет уже существующий файл requirements.txt.

$ pip freeze > requirements.txt

Конфигурация ASGI приложения хранится в "ините" базового каталога приложения, открываю этот файл в текстовом редакторе Vim.

$ vim webapp/__init__.py

В это файл необходимо дописать пару процедур импорта. Для реализации поставленной цели мне потребуются два библиотечных класса из состава Starlette: Middleware и SessionMiddleware.

from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware

На основе этих классов создаю объект с именем middleware следующего содержания.

middleware = [
    Middleware(
        SessionMiddleware,
        secret_key=settings('SECRET_KEY'),
        max_age=settings.get('SESSION_LIFETIME', cast=int))]

В этом коде следует обратить внимание, что в качестве значений для параметров экземпляра класса SessionMiddleware я указываю соответствующие поля файла настроек .venv, которые добавлю в этот файл на следующем шаге.

Только что созданный объект middleware дописываю в качестве значения одноимённого параметра в процедуру инициализации объекта app.

app = StApp(
    debug=settings.get('DEBUG', cast=bool),
    routes=[...],
    middleware=middleware,
    exception_handlers=errs)

DUTzCfzGG4.png

Перехожу в командный режим редактора, сохраняю изменения в файл и открываю файл настроек приложения.

:split .env

В конец этого файла дописываю два новых параметра.

SECRET_KEY='secret key'
SESSION_LIFETIME=1296000

Поскольку в рамках этого описания я запускаю разрабатываемое web-приложение в отладочном режиме, в качестве значения для SECRET_KEY я использую простую строку. В поле SESSION_LIFETIME я задаю числовое значение — время жизни сессии в секундах. Если пересчитать использованное число, то получится 15 земных суток. Задавая время жизни сессии следует помнить одну простую вещь: чем дольше живёт сессия, тем больше шансов у злоумышленников взломать её защиту.

Поскольку файл настроек числится в исключениях Git-хранилища, изменения этого файла необходимо сохранить в шаблон. Выполняю в командном режиме Vim следующую команду.

:!cp .env env_template

С этого момента разрабатываемое web-приложение получило защищённую шифрованием сессию, а разработчик — возможность эту сессию использовать.

Тестируем в браузере

Только что сделанные в конфигурации проекта изменения конечно же необходимо протестировать. Для этого в терминале с активным виртуальным окружением запускаю отладочный сервер, следую в браузер и стучусь по адресу стартовой страницы приложения. Разглядывать полученные результаты этих действий начнём с окна терминала.

GYgJt5pDYg.png

Как видно на снимае экрана выше, в отчёте отладочного сервера отсутствуют сообщения интерпретатора Python об ошибках и исключениях, это значит, что отредактированный код работает в штатном режиме, в полном соответствии с замыслом. Перемещаюсь в окно браузера, в этом окне активирую инструменты разработчика (ctrl+shift+i) и перемещаюсь на вкладку Application. Вот как выглядит это окно на моём рабочем столе.

CKuOniQuYx.png

Меня интересуют Cookies, и снимок экрана демонстрирует, что при запросе по указанному URL у клиента эти самые cookies отсутствуют.

А теперь я попытаюсь использовать сессию Starlette и отдать в ответе сервера на стартовой странице приложения эти самые cookies произвольного содержания. Для этого в соседнем терминале с активным текстовым редактором Vim открываю файл views.py из каталога main.

:tabnew webapp/main/views.py

В этом файле нахожу функцию представления с именем show_index и дописываю в тело этой функции одну единственную процедуру следующего вида.

    request.session['_uid'] = '01'

XRaF54sAxA.png

Снова перемещаюсь в окно браузера с открытой стартовой страницей и пробую обновить страницу соответствующей штатной кнопкой в панели инструментов этого окна. После обновления страницы опять смотрю на вкладку Application.

XPyU3r6nXD.png

Как видно на снимке экрана, теперь у клиента на запрошенном домене есть cookies с именем session, и они будут сопровождать каждый отправляемый этим браузером запрос на этот домен. В этом несложно убедиться, посмотрев на заголовки запросов на адресах /robots.txt и /favicon.ico, в этих заголовках присутствует соответствующее поле.

Прячем SECRET_KEY от Git

В файле настроек проекта на текущий момент появилось поле SECRET_KEY — этим ключом зашифрована только что подключенная сессия. И этот ключ необходимо спрятать от хранилища Git вместе с другими критически важными данными, которые в файле настроек появятся уже на ближайших этапах разработки. Файл настроек, хоть и указан в исключениях файла .gitignore, в процессе разработки будет редактироваться много раз, и каждая такая редакция будет сопровождаться копированием этого файла в шаблон env_template, который в Git фиксируется. А это значит, что есть риск засветить в публичном хранилище критически важные данные.

Кроме этого, поле SDESC файла настроек должно содержать строку длиной в 160 символов, и такая длинная строка делает файл настроек неудобным для чтения в редакторе Vim с заданными настройками. С этим полем тоже хочется что-нибудь сделать, руки чешутся. Я поступлю следующим образом...

Вновь открываю в текстовом редакторе "инит" базового шаблона.

:edit webapp/__init__.py

Под процедуры импорта дописываю в этот файл следующий код.

try:
    from .tuning import SECRET_KEY, SDESC
    if SECRET_KEY:
        settings.file_values['SECRET_KEY'] = SECRET_KEY
    if SDESC:
        settings.file_values['SDESC'] = SDESC
except ModuleNotFoundError:
    pass

Вот как это выглядит в окне моего текстового редактора.

YryRhEJ6y8.png

В этом коде я пытаюсь в рамках процедуры try ... except импортировать из файла tuning.py из базового каталога приложения необходимые мне константы, и отдаю значения этих констант соответствующим ключам объекта settings. Обращаю внимание, что tuning.py на текущий момент не существует и, в принципе, может не существовать вообще.

Файл, в котором я буду хранить критически важные данные, не должен попасть в Git, открываю в текстовом редакторе .gitignore.

:edit .gitignore

В этот файл добавляю одну единственную строчку, как показано на снимке экрана ниже.

h99ITcFGh6.png

Создаю с помощью текстового редактора новый файл в базовом каталоге приложения.

:edit webapp/tuning.py

И уже в этом файле определяю перечисленные в процедуре try ... except "инита" базового каталога константы.

SECRET_KEY='y$)Apgc3OYYla&SK'
SDESC='''Демонстрационное web-приложение на Python и Starlette, с пошаговым
процессом разработки, отладки и тестирования.'''.replace('\n', ' ')

Сохраняю изменения всех файлов, и задаюсь вопросом... А как внесённые в код правки протестировать?

Содержимое тега <meta> можно проверить в браузере, посмотрев исходный код стартовой страницы. А протестировать SECRET_KEY мне поможет так называемый отладочный print. Открываю в текстовом редакторе файл views.py из каталога main.

:tabnew webapp/main/views.py

Нахожу в этом файле функцию представления show_index и дописываю в тело этой функции вызов бибилиотечной функции print, отдав ей в качестве параметра интересующее меня поле SECRET_KEY, доступ к нему можно получить с помощью объекта request.

mlry0XZxeG.png

Сохраняю изменения в файл. Перехожу в окно браузера и обновляю в нём стартовую страницу. Посмотреть, что именно напечатает отладочный print, можно в терминале с отладочным сервером, вот как выглядит окно этого терминала на моём рабочем столе.

pzN0UOZkYr.png

Как видно на снимке экрана, в настройках приложения в поле SECRET_KEY хранится значение соответствующей константы файла tuning.py. При развёртывании приложения на сервер я без труда восстановлю этот файл по содержанию "инита" базового каталога, либо восстановлю и отредактирую файл .env, его серверная копия в шаблон не копируется никогда, таким образом крически важные данные в Git не попадут.

Подводим промежуточный итог

Все цели текущего этапа разработки достигнуты. В приложении появилась защищённая сессия, которую невозможно модифицировать на стороне, что даёт мне некоторые возможности для проектирования сразу нескольких функциональных элементов разрабатываемого приложения. В одном из следующих выпусков этого цикла статей я покажу разработку отправки коротких сообщений между запросами к серверу с помощью сессии. Текущую версию кода приложения можно увидеть в моём профиле на github.com по этой короткой ссылке.

Напоминаю, что поддержать продолжение этого цикла статей вы можете своей активностью в блоге. Ваши посещения, подписки, лайки, комментарии и донаты имеют большое значение. Не оставайтесь в стороне, будет интересно...