Локальный стенд безопасности
Модель угроз описывает библиотеку заметок и действия двух владельцев. Теперь создадим минимальное приложение, которое можно изучать отдельно от настоящего сайта. На этом этапе оно показывает вымышленные записи без входа. Такое начальное состояние помогает увидеть границы, которые будем добавлять далее; оно не обозначает готовое размещение личного сервиса.
Учебный проект называется security-lab. Используем Python 3.12 и API Flask 3.1. Примеры этой серии сохранены как исходники, но при подготовке текстов не запускались. Ожидаемые ответы ниже описывают самостоятельную практику читателя, а не выполненные запросы к ProfessorWeb.
Отдельное окружение
Создайте новую папку проекта. Внутри неё находятся исходник приложения, шаблоны и будущая локальная база. Не нужно переносить туда резервную копию действующего сайта, настоящие пароли или токены. Для объяснения владения достаточно двух искусственных заметок с постоянными номерами.
Файл зависимостей начального состояния содержит выбранную линию API:
Flask>=3.1,<3.2
Werkzeug>=3.1,<3.2
Диапазон не является воспроизводимым списком всех транзитивных версий. При самостоятельной установке нужно сохранить фактически разрешённый состав окружения. Позднее рассмотрим фиксацию зависимостей и проверку известных уязвимостей. Сейчас важно отделить библиотеки стенда от библиотек генератора основного сайта.
Для будущего запуска используйте отдельное виртуальное окружение. Команды относятся только к подготовленной папке:
python3.12 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python -m flask --app base run --host 127.0.0.1 --port 4270
Порт 4270 выбран для учебного приложения. Адрес 127.0.0.1 оставляет сервер на локальной машине. Не заменяйте его внешним адресом ради просмотра примера: сначала нужно понять отсутствующие механизмы и способ промышленного обслуживания.
Минимальное приложение
Ниже полный файл base.py. Он отвечает на проверку здоровья, выводит список JSON и возвращает одну запись. Пока данные находятся в словаре, чтобы не смешивать устройство HTTP с настройкой базы.
from flask import Flask, abort, jsonify
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 16_384
NOTES = {
1: {"id": 1, "owner": "alice", "title": "План Markdown",
"text": "Отделим текст урока от шаблона."},
2: {"id": 2, "owner": "boris", "title": "Проверка выпуска",
"text": "Сверим старый адрес и страницу каталога."},
}
@app.get("/health")
def health():
return jsonify(status="ok", project="security-lab")
@app.get("/notes")
def notes():
return jsonify(items=list(NOTES.values()))
@app.get("/notes/<int:note_id>")
def note(note_id):
item = NOTES.get(note_id)
if item is None:
abort(404)
return jsonify(item)
Поле owner пока только обозначает учебного владельца. Его наличие не проверяет право доступа. Если оба документа возвращаются любому запросу, приложение ещё не выполнило требование первого урока. Позднее имя заменится связью с пользователем в базе, а обработчик станет выбирать только разрешённый объект.
Маршрут использует целочисленный конвертер. Он отделяет номер объекта от произвольной строки пути. Но номер 1 остаётся допустимым для любого посетителя: проверка формы значения и проверка полномочия решают разные задачи. Это различие будет постоянно встречаться в курсе.
Ожидаемые ответы
При самостоятельном запуске проверка здоровья должна возвращать JSON с названием проекта. Запрос заметки 1 должен показывать запись Alice; неизвестный номер — ответ 404. Полезно сохранить код ответа, тип содержимого и тело, а не только увидеть сообщение в браузере.
GET /health → 200, application/json
GET /notes/1 → 200, учебная заметка alice
GET /notes/999 → 404
Коды в этой таблице являются ожиданиями. Если получен 500, проблема находится в выполнении приложения, а не в правах на отсутствующий документ. Если вместо JSON возвращается страница другого сервиса, сначала проверьте адрес и порт. Неправильное окружение легко создаёт ложное впечатление о поведении кода.
Для начальных наблюдений достаточно браузера или отдельного HTTP-клиента. Не требуется сканировать внешний сайт. Все будущие изменения будут относиться к известным маршрутам, двум вымышленным владельцам и небольшому набору значений.
Что хранить рядом с кодом
Создайте короткое описание стенда: версия Python, файл зависимостей, команда запуска, адрес и исходные записи. После появления базы добавим способ её подготовки. Такая запись предотвращает ситуацию, когда текст урока относится к одному состоянию, а читатель случайно запускает другое.
Локальные данные и секреты не являются исходниками. Для заключительного приложения предусмотрим исключение виртуального окружения, базы, загруженных изображений и файла окружения из Git. Исключение из Git само по себе не ограничивает HTTP-доступ: публичная папка и маршруты должны оставаться отдельной границей.
Журнал разработчика тоже относится к стенду. В начале сервер может выводить путь запроса; в уроке о журналах уточним договор и исключим чувствительные параметры. Не используйте настоящие пароли даже в экспериментальных строках: учебная проверка должна оставаться воспроизводимой без личных данных.
Начальное и заключительное состояние
Файл base.py показывает только отправную точку. В конце курса появится app.py с шаблонами, SQLite, входом, проверкой владельца и ограниченными операциями. Между ними уроки вводят отдельные механизмы и явно называют новые определения.
Часть листингов является самостоятельной демонстрацией, часть — фрагментом заключительного приложения. Перед кодом будем обозначать эту разницу. Не объединяйте все встреченные варианты обработчика в один файл: два одинаковых маршрута не создают последовательность уроков, а конфликтуют при регистрации.
Встроенный сервер Flask предназначен для разработки. Способ его применения описан в документации Flask. Он позволяет наблюдать HTTP-договор, но не заменяет процесс размещения из отдельного курса деплоя.
Состояние при перезапуске
Словарь NOTES существует в памяти процесса. Если в демонстрации изменить значение и перезапустить base.py, первоначальные строки появятся снова. Это ожидаемое свойство отправной точки. После введения SQLite состояние переживает перезапуск, поэтому сравнивать оба варианта нужно с учётом места хранения.
Health пока отвечает только за живой процесс и известный маршрут. Он не проверяет пароль, связь владельцев или восстановимость данных. Успешный 200 нельзя переносить на требование «все личные записи защищены». Для него будут другие обращения и другая исходная личность.
Если импорт Flask не сработал, сначала проверьте выбранный интерпретатор. Установка в одну Python-среду и запуск из другой создают разные условия. Также не называйте свой файл flask.py: он способен затенить импорт библиотеки. Такая ошибка возникает до проверки HTTP-правил и не является отказом механизма авторизации.
Сохраняйте начальные наблюдения отдельно от финальных. Например, публичная выдача двух записей правильна для base.py и неправильна для личного app.py. Без имени состояния одна таблица ответов будет выглядеть противоречиво, хотя она относится к двум разным этапам обучения.
Теперь у требований появились конкретные объекты и маршруты. Следующий урок определит, какие значения допускаются на входе, где заканчивается проверка браузера и какой ответ получает неверная форма.