Сессия в web-приложении на Python и Starlette
programming
В web-приложении на Python и Starlette, разработку которого мы исследуем в этом цикле статей, уже очень скоро появится собственная система аутентификации пользователей, обеспечивающая самостоятельную регистрацию аккаунта и авторизацию пользователя на основе логина и пароля. Одним из элементов этой системы станет защищённая сессия Starlette — подписанные и зашифрованные cookies, которые, вероятно, можно расшифровать на стороне, но вряд ли возможно подделать. В очередном выпуске этого цикла я займусь подключением сессии в приложение.
Сессия Starlette
Сессия Starlette реализована как middleware класс и обеспечивает заданное поведение на протяжении всего исполнения приложения. Сессия даёт возможность определить в заданном разработчиком месте приложения, обычно в одном из обработчиков запросов, подписанные и зашифрованные cookies, которые впоследствии посылаются серверу с каждым запросом клиента. В документации Starlette утверждается, что данные сессии возможно прочесть, но невозможно модифицировать, и это свойство в процессе разработки мне очень пригодится.
В рамках описываемого в этом цикле статей web-приложения я планирую использовать сессию Starlette в двух вариантах:
-
Для обмена короткими сообщениями между запросами при переадресациях;
-
Для идентификации клиента на одном из этапов авторизации пользователя в сервисе.
На текущем этапе разработки, увы, пока невозможно продемонстрировать все приёмы работы с сессией 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)
Перехожу в командный режим редактора, сохраняю изменения в файл и открываю файл настроек приложения.
:split .env
В конец этого файла дописываю два новых параметра.
SECRET_KEY='secret key'
SESSION_LIFETIME=1296000
Поскольку в рамках этого описания я запускаю разрабатываемое web-приложение в отладочном режиме, в качестве значения для SECRET_KEY
я использую простую строку. В поле SESSION_LIFETIME
я задаю числовое значение — время жизни сессии в секундах. Если пересчитать использованное число, то получится 15 земных суток. Задавая время жизни сессии следует помнить одну простую вещь: чем дольше живёт сессия, тем больше шансов у злоумышленников взломать её защиту.
Поскольку файл настроек числится в исключениях Git-хранилища, изменения этого файла необходимо сохранить в шаблон. Выполняю в командном режиме Vim следующую команду.
:!cp .env env_template
С этого момента разрабатываемое web-приложение получило защищённую шифрованием сессию, а разработчик — возможность эту сессию использовать.
Тестируем в браузере
Только что сделанные в конфигурации проекта изменения конечно же необходимо протестировать. Для этого в терминале с активным виртуальным окружением запускаю отладочный сервер, следую в браузер и стучусь по адресу стартовой страницы приложения. Разглядывать полученные результаты этих действий начнём с окна терминала.
Как видно на снимае экрана выше, в отчёте отладочного сервера отсутствуют сообщения интерпретатора Python об ошибках и исключениях, это значит, что отредактированный код работает в штатном режиме, в полном соответствии с замыслом. Перемещаюсь в окно браузера, в этом окне активирую инструменты разработчика (ctrl+shift+i
) и перемещаюсь на вкладку Application. Вот как выглядит это окно на моём рабочем столе.
Меня интересуют Cookies, и снимок экрана демонстрирует, что при запросе по указанному URL у клиента эти самые cookies отсутствуют.
А теперь я попытаюсь использовать сессию Starlette и отдать в ответе сервера на стартовой странице приложения эти самые cookies произвольного содержания. Для этого в соседнем терминале с активным текстовым редактором Vim открываю файл views.py
из каталога main
.
:tabnew webapp/main/views.py
В этом файле нахожу функцию представления с именем show_index
и дописываю в тело этой функции одну единственную процедуру следующего вида.
request.session['_uid'] = '01'
Снова перемещаюсь в окно браузера с открытой стартовой страницей и пробую обновить страницу соответствующей штатной кнопкой в панели инструментов этого окна. После обновления страницы опять смотрю на вкладку Application.
Как видно на снимке экрана, теперь у клиента на запрошенном домене есть 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
Вот как это выглядит в окне моего текстового редактора.
В этом коде я пытаюсь в рамках процедуры try ... except импортировать из файла tuning.py
из базового каталога приложения необходимые мне константы, и отдаю значения этих констант соответствующим ключам объекта settings
. Обращаю внимание, что tuning.py
на текущий момент не существует и, в принципе, может не существовать вообще.
Файл, в котором я буду хранить критически важные данные, не должен попасть в Git, открываю в текстовом редакторе .gitignore
.
:edit .gitignore
В этот файл добавляю одну единственную строчку, как показано на снимке экрана ниже.
Создаю с помощью текстового редактора новый файл в базовом каталоге приложения.
: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
.
Сохраняю изменения в файл. Перехожу в окно браузера и обновляю в нём стартовую страницу. Посмотреть, что именно напечатает отладочный print, можно в терминале с отладочным сервером, вот как выглядит окно этого терминала на моём рабочем столе.
Как видно на снимке экрана, в настройках приложения в поле SECRET_KEY
хранится значение соответствующей константы файла tuning.py
. При развёртывании приложения на сервер я без труда восстановлю этот файл по содержанию "инита" базового каталога, либо восстановлю и отредактирую файл .env
, его серверная копия в шаблон не копируется никогда, таким образом крически важные данные в Git не попадут.
Подводим промежуточный итог
Все цели текущего этапа разработки достигнуты. В приложении появилась защищённая сессия, которую невозможно модифицировать на стороне, что даёт мне некоторые возможности для проектирования сразу нескольких функциональных элементов разрабатываемого приложения. В одном из следующих выпусков этого цикла статей я покажу разработку отправки коротких сообщений между запросами к серверу с помощью сессии. Текущую версию кода приложения можно увидеть в моём профиле на github.com по этой короткой ссылке.
Напоминаю, что поддержать продолжение этого цикла статей вы можете своей активностью в блоге. Ваши посещения, подписки, лайки, комментарии и донаты имеют большое значение. Не оставайтесь в стороне, будет интересно...