Безопасность веб-приложений

Концентрированный курс по защите бэкенда от уязвимостей из списка OWASP Top 10. Вы освоите принципы защиты пользовательских данных и научитесь настраивать современную аутентификацию с использованием JWT и OAuth2.

1. Введение в безопасность бэкенда и обзор уязвимостей OWASP Top 10

Введение в безопасность бэкенда и обзор уязвимостей OWASP Top 10

Бэкенд — это фундамент любого веб-приложения. Именно здесь хранятся пользовательские данные, обрабатываются платежи и принимаются критически важные бизнес-решения. Фронтенд можно обойти, мобильное приложение — декомпилировать, но серверная часть обязана оставаться неприступной крепостью. Информационная безопасность на уровне бэкенда требует понимания того, как злоумышленники мыслят и какие векторы атак используют.

Базовая концепция защиты строится на так называемой триаде ЦИА (CIA triad). Это модель, определяющая три главных свойства защищенной системы:

Конфиденциальность (Confidentiality*): данные доступны только тем, кто имеет на это право. Целостность (Integrity*): информация не может быть изменена несанкционированным образом. Доступность (Availability*): система и данные доступны легитимным пользователям в нужный момент времени.

> Безопасность — это не продукт, а процесс. Нельзя один раз написать «безопасный код» и забыть об этом. Архитектура должна быть готова к тому, что новые векторы атак появляются ежедневно.

Для систематизации знаний об угрозах существует OWASP (Open Worldwide Application Security Project) — международная некоммерческая организация, занимающаяся безопасностью веб-приложений. Раз в несколько лет они выпускают OWASP Top 10 — рейтинг самых критичных уязвимостей. Знание этого списка является абсолютным минимумом для любого Middle-разработчика.

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

| Позиция | Название уязвимости | Суть проблемы | Пример из реальной жизни | | :--- | :--- | :--- | :--- | | A01 | Нарушение контроля доступа | Пользователь получает доступ к чужим данным или функциям администратора | Смена ID в URL позволяет читать чужие сообщения | | A02 | Криптографические сбои | Утечка чувствительных данных из-за слабого шифрования или его отсутствия | Хранение паролей в базе данных в открытом виде | | A03 | Инъекции | Недоверенные данные интерпретируются как команды | Выполнение произвольного SQL-запроса через форму поиска | | A05 | Небезопасная конфигурация | Ошибки в настройках серверов, фреймворков или баз данных | Оставленный включенным режим отладки на боевом сервере | | A06 | Уязвимые компоненты | Использование библиотек с известными дырами в безопасности | Устаревшая версия пакета в requirements.txt |

Инъекции (Injection)

Инъекции происходят, когда приложение отправляет нефильтрованные данные пользователя интерпретатору в составе команды или запроса. Самый известный вид — SQL-инъекция (SQLi). Если бэкенд формирует запросы к базе данных путем простой конкатенации строк, злоумышленник может внедрить собственный SQL-код.

Представим систему, где поиск пользователя реализован через сырой SQL-запрос.

Если злоумышленник передаст в качестве параметра id строку 1 OR 1=1, итоговый запрос примет вид SELECT * FROM users WHERE id = 1 OR 1=1. Поскольку условие всегда истинно, база данных вернет записи абсолютно всех пользователей. В современных фреймворках вроде Django или FastAPI (при использовании SQLAlchemy) эта проблема решается применением ORM, которые автоматически параметризуют запросы.

Нарушение контроля доступа (Broken Access Control)

Эта уязвимость поднялась на первое место в последнем отчете OWASP. Она возникает, когда приложение не проверяет, имеет ли текущий авторизованный пользователь права на выполнение конкретного действия.

Частный и очень распространенный случай — небезопасная прямая ссылка на объект (IDOR - Insecure Direct Object Reference). Допустим, API возвращает данные профиля по эндпоинту /api/v1/profiles/1052/. Если разработчик проверяет только факт авторизации пользователя, но не проверяет, принадлежит ли профиль именно этому пользователю, атакующий может написать простой скрипт.

Скрипт будет перебирать ID: , , и так далее. Если в системе пользователей, злоумышленник скачает всю базу персональных данных, просто меняя одно число в URL. Защита от IDOR требует внедрения проверок на уровне бизнес-логики: каждый запрос к ресурсу должен сопровождаться проверкой прав владения этим ресурсом.

Криптографические сбои (Cryptographic Failures)

Ранее эта категория называлась «Утечка чувствительных данных». Главная ошибка бэкенд-разработчиков здесь — неправильное хранение паролей и токенов. Пароли никогда не должны храниться в открытом виде или хешироваться устаревшими алгоритмами вроде MD5 или SHA-1.

Современный стандарт хранения паролей требует использования алгоритмов с адаптивной сложностью, таких как Argon2, bcrypt или scrypt. Эти алгоритмы намеренно работают медленно.

Время, необходимое злоумышленнику для взлома украденной базы методом грубой силы, описывается простой зависимостью:

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

Алгоритмы вроде MD5 позволяют современным видеокартам достигать скорости хешей в секунду. Адаптивные алгоритмы искусственно снижают до нескольких десятков вычислений в секунду, делая перебор экономически нецелесообразным. Кроме того, к каждому паролю перед хешированием должна добавляться уникальная случайная строка — соль (salt), что делает невозможным использование заранее вычисленных радужных таблиц (rainbow tables).

Небезопасная конфигурация (Security Misconfiguration)

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

В Django за это отвечает параметр DEBUG = True в файле settings.py. Если в приложении произойдет ошибка, фреймворк выведет на экран подробный стек вызовов, значения всех локальных переменных, пути к файлам на сервере и даже пароли от базы данных, если они попали в контекст ошибки.

> Никогда не доверяйте настройкам по умолчанию при переносе приложения в production-среду. Всегда используйте переменные окружения для управления конфигурацией.

Другой пример небезопасной конфигурации — отсутствие лимитов на количество запросов (Rate Limiting). Если API авторизации позволяет делать 500 запросов в секунду с одного IP-адреса, это открывает прямую дорогу для атак методом грубой силы (brute-force).

Уязвимые и устаревшие компоненты

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

Если проект использует старую версию библиотеки Pillow для обработки изображений или устаревшую версию PyJWT для работы с токенами, злоумышленник может использовать публично известные эксплойты. В мире информационной безопасности существует база данных CVE (Common Vulnerabilities and Exposures), где регистрируются все найденные дыры.

Для защиты необходимо регулярно обновлять зависимости и использовать инструменты статического анализа. Например, утилита safety для Python проверяет файл requirements.txt по базе известных уязвимостей и предупреждает, если какой-то из пакетов скомпрометирован.

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

2. Принципы защиты пользовательских данных и безопасное хранение паролей

Принципы защиты пользовательских данных и безопасное хранение паролей

Утечка базы данных — это страшный сон любого бизнеса. Если злоумышленники получат доступ к серверам, они не должны извлечь из украденной информации никакой пользы. Защита персональных данных (PII - Personally Identifiable Information) строится на принципе глубокой эшелонированной обороны, где безопасность паролей и механизмов авторизации играет ключевую роль.

Эволюция хранения паролей

Хранить пароли в открытом виде — преступление против пользователей. Однако простое хеширование с помощью алгоритмов вроде MD5 или SHA-256 сегодня также считается уязвимостью. Главная проблема быстрых хеш-функций заключается в их скорости. Современная видеокарта стоимостью 1000 долл. способна перебирать десятки миллиардов SHA-256 хешей в секунду.

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

где — энтропия, — размер алфавита (количество возможных символов), а — длина пароля.

Если пользователь задал пароль из 8 строчных английских букв, то , а . Энтропия такого пароля составит бит. Для взлома такого хеша методом грубой силы современному оборудованию потребуются доли секунды.

Чтобы нивелировать скорость оборудования атакующих, бэкенд-разработчики используют алгоритмы с адаптивной сложностью: bcrypt, scrypt или Argon2. Они требуют значительных затрат процессорного времени и оперативной памяти для вычисления одного хеша.

> Криптографическая хеш-функция для паролей должна быть намеренно медленной. Если легитимный пользователь ждет 0.5 секунды при входе в систему — это незаметно. Но для хакера, перебирающего миллионы комбинаций, задержка в 0.5 секунды делает атаку экономически невыгодной.

Для защиты от предварительно вычисленных таблиц (радужных таблиц) к каждому паролю добавляется соль (salt) — уникальная случайная строка, которая генерируется для каждого пользователя и хранится в базе данных вместе с хешем. Дополнительным слоем защиты выступает перец (pepper) — секретный ключ, который добавляется к паролю, но хранится не в базе данных, а в переменных окружения сервера.

Пример безопасного хеширования с использованием библиотеки passlib в Python:

Современная аутентификация: токены и сессии

После того как пользователь успешно подтвердил свой пароль, сервер должен «запомнить» его, чтобы не запрашивать учетные данные при каждом следующем клике. Исторически для этого использовались серверные сессии, но в современных распределенных системах стандартом де-факто стал JWT (JSON Web Token).

JWT — это компактный формат передачи данных, который криптографически подписан сервером. Он состоит из трех частей, разделенных точками:

  • Заголовок (Header): содержит тип токена и алгоритм подписи.
  • Полезная нагрузка (Payload): данные пользователя (ID, роли) и время жизни токена.
  • Подпись (Signature): создается на основе первых двух частей и секретного ключа сервера.
  • Главное преимущество JWT — отсутствие состояния (stateless). Серверу не нужно обращаться к базе данных или кэшу, чтобы проверить валидность токена. Достаточно проверить криптографическую подпись.

    | Характеристика | Серверные сессии (Cookies) | JSON Web Tokens (JWT) | | :--- | :--- | :--- | | Хранение состояния | На сервере (БД или Redis) | На стороне клиента (Local Storage / Memory) | | Масштабируемость | Требует синхронизации между серверами | Идеально для микросервисов, не требует синхронизации | | Отзыв доступа | Мгновенно (удаление сессии из БД) | Сложно (токен валиден до истечения срока жизни) |

    Поскольку отозвать скомпрометированный JWT до истечения его срока жизни сложно, применяется паттерн с двумя токенами. Access Token (токен доступа) живет очень недолго, например, 15 минут. Refresh Token (токен обновления) живет долго, например, 30 дней, и хранится в базе данных. Когда Access Token протухает, клиент отправляет Refresh Token на специальный эндпоинт, чтобы получить новую пару.

    Делегирование доступа: OAuth2

    Часто веб-приложениям требуется доступ к ресурсам пользователя на других платформах (например, импорт контактов из Google или авторизация через GitHub). Запрашивать у пользователя пароль от его почты — грубейшее нарушение безопасности. Для решения этой задачи был создан протокол OAuth2.

    OAuth2 позволяет приложению получить ограниченный доступ к данным пользователя без передачи пароля. Процесс выглядит следующим образом:

  • Приложение перенаправляет пользователя на сервер авторизации (например, Google).
  • Пользователь видит запрос: «Приложение X хочет получить доступ к вашему email».
  • Пользователь нажимает «Разрешить».
  • Сервер авторизации возвращает приложению временный код.
  • Бэкенд приложения обменивает этот код на Access Token для работы с API Google.
  • Если приложение запрашивает доступ к списку контактов, токен будет иметь строго ограниченную область видимости (scope). Даже если хакер украдет этот токен, он сможет только прочитать контакты, но не сможет сменить пароль от почты или удалить аккаунт.

    Защита данных в покое и в пути

    Безопасность паролей и токенов бессмысленна, если сами данные передаются или хранятся в открытом виде. В архитектуре безопасности выделяют два состояния данных:

    Данные в пути (Data in transit): информация, передаваемая между клиентом и сервером. Защищается с помощью протокола TLS (HTTPS). Использование обычного HTTP в современных веб-приложениях недопустимо, так как позволяет перехватить трафик атакой «человек посередине» (Man-in-the-Middle*). Данные в покое (Data at rest*): информация, физически записанная на диски серверов баз данных. Критичные данные (номера кредитных карт, медицинские записи) должны шифроваться на уровне приложения с использованием симметричных алгоритмов, таких как AES-256, до того, как они попадут в базу данных.

    Представим медицинскую систему, хранящую диагнозы 50 000 пациентов. Если база данных не зашифрована, любой сотрудник с доступом к резервным копиям может прочитать эту информацию. При использовании AES-256 и правильном управлении ключами шифрования (например, через AWS KMS или HashiCorp Vault), украденный дамп базы данных будет представлять собой бесполезный набор случайных байтов.

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

    3. Защита бэкенда от инъекций, XSS, CSRF и других распространенных атак

    Защита бэкенда от инъекций, XSS, CSRF и других векторов атак

    Надежное хранение паролей и использование современных токенов авторизации теряют смысл, если само приложение уязвимо к манипуляциям с данными. Главное правило безопасности любого веб-сервиса звучит просто: никогда не доверяй пользовательскому вводу. Любая строка, число или файл, пришедшие от клиента, должны рассматриваться как потенциальная угроза, пока не пройдут строгую валидацию.

    Инъекции: когда данные становятся кодом

    Инъекция (Injection) происходит, когда нефильтрованные данные от пользователя передаются интерпретатору как часть команды или запроса. Самый известный вид этой уязвимости — SQL-инъекция.

    Поскольку в современных фреймворках вроде Django или FastAPI (в связке с SQLAlchemy) разработчики используют ORM, риск классических SQL-инъекций значительно снижен. ORM автоматически экранирует параметры. Однако уязвимость возвращается, как только разработчик решает написать «сырой» SQL-запрос для оптимизации производительности.

    Рассмотрим пример уязвимого кода на Python:

    Если злоумышленник передаст в качестве username строку admin' OR '1'='1, итоговый запрос примет вид SELECT * FROM users WHERE username = 'admin' OR '1'='1'. База данных вернет записи всех пользователей, так как условие 1=1 всегда истинно.

    Для защиты необходимо использовать параметризованные запросы, где база данных сама безопасно подставляет значения:

    Помимо баз данных, инъекции могут быть направлены на операционную систему (Command Injection). Если бэкенд вызывает системные утилиты через os.system() или subprocess.run(), передавая туда пользовательский ввод без проверки, хакер может выполнить произвольный код на сервере.

    Межсайтовая подделка запроса (CSRF)

    CSRF (Cross-Site Request Forgery) — это атака, при которой злоумышленник заставляет браузер аутентифицированного пользователя выполнить нежелательное действие на доверенном сайте.

    Механика атаки выглядит следующим образом:

  • Пользователь авторизуется в банковском приложении, и браузер сохраняет сессионную cookie.
  • В соседней вкладке пользователь открывает вредоносный сайт.
  • Вредоносный сайт содержит скрытую форму, которая автоматически отправляет POST-запрос на эндпоинт перевода денег в банковском приложении.
  • Браузер автоматически прикрепляет к этому запросу сессионную cookie банка.
  • Бэкенд банка видит валидную сессию и выполняет перевод.
  • Для защиты от CSRF применяются два основных метода. Первый — использование CSRF-токенов (Anti-CSRF Tokens). Сервер генерирует уникальный криптографически стойкий токен и передает его клиенту. При любом запросе, изменяющем состояние (POST, PUT, DELETE), клиент обязан передать этот токен в теле запроса или в заголовках. Вредоносный сайт не может прочитать этот токен из-за политики одинакового источника (Same-Origin Policy).

    Второй метод — настройка атрибута SameSite для файлов cookie. Если установить SameSite=Lax или SameSite=Strict, браузер перестанет отправлять cookie при кросс-доменных POST-запросах.

    Межсайтовый скриптинг (XSS) глазами бэкендера

    XSS (Cross-Site Scripting) традиционно считается фронтенд-уязвимостью. Суть атаки заключается во внедрении вредоносного JavaScript-кода в страницу, которую просматривают другие пользователи. Однако бэкенд играет критическую роль в предотвращении этой угрозы, особенно при защите от хранимого XSS (Stored XSS).

    Если приложение позволяет пользователям оставлять комментарии, и бэкенд сохраняет их в базу данных без очистки, любой скрипт вида <script>alert('XSS')</script> будет выполнен в браузере каждого, кто откроет страницу с этим комментарием.

    > Задача бэкенда — гарантировать, что любые данные, отдаваемые клиенту, безопасны для отображения. Это достигается через валидацию на входе и санитаризацию (очистку опасных тегов) перед сохранением.

    Для очистки HTML-контента в Python часто используется библиотека bleach, которая удаляет все теги, кроме явно разрешенных:

    Дополнительным эшелоном защиты служит заголовок Content-Security-Policy (CSP). Бэкенд может отправить этот HTTP-заголовок, чтобы указать браузеру, из каких источников разрешено загружать и выполнять скрипты. Даже если хакеру удастся внедрить вредоносный код, строгая политика CSP заблокирует его выполнение.

    | Характеристика | XSS (Cross-Site Scripting) | CSRF (Cross-Site Request Forgery) | | :--- | :--- | :--- | | Цель атаки | Выполнение скрипта в браузере жертвы | Выполнение действия от имени жертвы | | Что крадут/используют | Данные пользователя, токены из Local Storage | Активную сессию (Cookies) браузера | | Главная защита на бэкенде | Санитаризация ввода, заголовки CSP | CSRF-токены, атрибут SameSite для Cookies |

    Ограничение частоты запросов (Rate Limiting)

    Даже идеально защищенный от инъекций код может стать жертвой атак грубой силы (Brute Force) или отказа в обслуживании (DDoS). Если эндпоинт авторизации не имеет ограничений, злоумышленник может перебирать тысячи паролей в секунду.

    Ограничение частоты запросов (Rate Limiting) позволяет задать лимиты на количество обращений к API от одного IP-адреса или пользователя за единицу времени. Математика защиты проста: если количество запросов за 1 минуту, сервер блокирует последующие попытки и возвращает HTTP-статус 429 Too Many Requests.

    Представим, что мы настраиваем лимиты для системы авторизации. Мы разрешаем не более 5 попыток входа в минуту для одного IP-адреса. Если хакер использует базу из 100 000 украденных паролей, при таком лимите ему потребуется 20 000 минут (почти 14 дней) непрерывной работы только с одного IP-адреса. Это делает атаку экономически нецелесообразной и легко обнаруживаемой системами мониторинга.

    В экосистеме FastAPI для этих целей часто применяется библиотека slowapi, а в Django — django-ratelimit. Они используют Redis или Memcached для быстрого подсчета количества запросов в распределенных системах.

    Безопасные HTTP-заголовки

    Помимо CSP, бэкенд должен устанавливать ряд других HTTP-заголовков для защиты клиента:

    Strict-Transport-Security (HSTS*): принудительно заставляет браузер использовать только защищенное HTTPS-соединение, исключая атаки с понижением степени защиты. X-Frame-Options: запрещает встраивание вашего сайта во фреймы на других ресурсах, защищая от кликджекинга (Clickjacking*). * X-Content-Type-Options: со значением nosniff запрещает браузеру угадывать MIME-тип контента, заставляя строго следовать типу, указанному сервером.

    Комплексная безопасность веб-приложения требует постоянного внимания к деталям. Использование современных фреймворков закрывает многие уязвимости из коробки, но понимание векторов атак позволяет разработчику не допускать критических ошибок при реализации нестандартной бизнес-логики.

    4. Настройка безопасной аутентификации и управление сессиями с помощью JWT

    Настройка безопасной аутентификации и управление сессиями с помощью JWT

    После того как пароль пользователя надежно захэширован алгоритмом с адаптивной сложностью и сохранен в базе данных, возникает следующая инженерная задача. Как серверу «запомнить» авторизованного пользователя, чтобы тому не приходилось отправлять свои учетные данные при каждом переходе на новую страницу? Для решения этой задачи применяются механизмы управления сессиями.

    Исторически стандартом де-факто были классические серверные сессии (Stateful). Сервер генерировал случайный идентификатор сессии (Session ID), отправлял его клиенту в виде файла cookie, а все данные о пользователе хранил в своей оперативной памяти или быстрой базе данных вроде Redis. Однако с ростом популярности микросервисной архитектуры и мобильных приложений этот подход стал узким местом при масштабировании.

    Переход к архитектуре без состояния

    Современные бэкенд-приложения стремятся к архитектуре Stateless (без сохранения состояния). В такой парадигме сервер не хранит информацию о текущих сессиях. Вместо этого вся необходимая информация криптографически подписывается и выдается самому клиенту в виде JSON Web Token (JWT).

    | Характеристика | Классические сессии (Stateful) | Токены JWT (Stateless) | | :--- | :--- | :--- | | Хранение состояния | На стороне сервера (БД, Redis, память) | На стороне клиента (Браузер, мобильное приложение) | | Масштабирование | Требует синхронизации сессий между серверами | Легко масштабируется, серверы независимы | | Нагрузка на БД | Запрос к хранилищу сессий при каждом обращении | Валидация происходит в памяти (проверка подписи) | | Отзыв доступа | Мгновенный (удаление записи из БД) | Затруднен до истечения срока жизни токена |

    > JWT — это открытый стандарт (RFC 7519), который определяет компактный и автономный способ безопасной передачи информации между сторонами в виде объекта JSON. Эта информация может быть проверена и ей можно доверять, поскольку она имеет цифровую подпись. > > Официальная документация JWT

    Анатомия токена: три кита JWT

    Визуально JWT выглядит как длинная строка из случайных символов, разделенная двумя точками на три блока: aaaaa.bbbbb.ccccc. Эти блоки представляют собой закодированные в формат Base64Url структуры данных.

  • Заголовок (Header): содержит тип токена (обычно "JWT") и алгоритм подписи (например, HMAC SHA256 или RSA).
  • Полезная нагрузка (Payload): содержит утверждения (Claims) — сами данные пользователя. Здесь обычно передают идентификатор пользователя (sub), его роль (role) и время истечения срока действия токена (exp).
  • Подпись (Signature): криптографическая гарантия того, что данные не были изменены в пути.
  • Для создания подписи сервер берет закодированные заголовок и полезную нагрузку, объединяет их через точку и хэширует с использованием секретного ключа, который известен только бэкенду.

    Рассмотрим пример генерации такого токена на Python с использованием библиотеки PyJWT:

    При получении запроса от клиента бэкенд извлекает токен, заново вычисляет подпись на основе своего секретного ключа и сравнивает ее с подписью в токене. Если они совпадают, сервер доверяет данным в Payload.

    Токен автоматически считается недействительным сервером, если выполняется математическое условие , где — время истечения срока действия токена, указанное в полезной нагрузке, а — текущее системное время сервера в формате Unix Timestamp.

    Паттерн Access и Refresh токенов

    Поскольку JWT невозможно отозвать (он валиден, пока не истечет его срок действия), делать токены долгоживущими крайне опасно. Если злоумышленник перехватит токен, который действует год, он получит доступ к аккаунту на весь этот год.

    Для баланса между безопасностью и удобством пользователя применяется связка из двух токенов:

    * Access-токен: короткоживущий токен (обычно от 5 до 15 минут). Используется для доступа к защищенным эндпоинтам API. * Refresh-токен: долгоживущий токен (от нескольких дней до месяцев). Используется исключительно для получения новой пары Access/Refresh токенов, когда старый Access-токен протухает.

    Представим пользователя, который работает в CRM-системе 8 часов подряд. Если Access-токен живет 15 минут, то за рабочий день приложение автоматически, в фоновом режиме, отправит Refresh-токен на сервер 32 раза, чтобы получить свежие ключи доступа. Пользователь этого даже не заметит, но если его Access-токен украдут, хакер сможет пользоваться им максимум 14 минут 59 секунд.

    Стратегия хранения: LocalStorage против Cookies

    Самая частая и критическая ошибка Middle-разработчиков при внедрении JWT — неправильный выбор места хранения токенов на стороне браузера.

    Многие фронтенд-разработчики предпочитают сохранять токены в LocalStorage или SessionStorage, так как это позволяет легко извлекать их с помощью JavaScript и подставлять в заголовок Authorization: Bearer <token>. Однако любое хранилище, доступное для JavaScript, уязвимо к атакам межсайтового скриптинга (XSS). Если хакер внедрит вредоносный скрипт на страницу, он сможет прочитать LocalStorage и отправить токены на свой сервер.

    Безопасный подход для веб-приложений заключается в использовании файлов cookie со строгими флагами безопасности:

    HttpOnly: запрещает доступ к cookie* из JavaScript. Даже при успешной XSS-атаке скрипт не сможет прочитать токен. Secure: гарантирует, что cookie* будет передаваться только по зашифрованному HTTPS-соединению. SameSite=Strict (или Lax): защищает от атак подделки межсайтовых запросов (CSRF), запрещая браузеру отправлять cookie* при запросах со сторонних доменов.

    При такой архитектуре бэкенд должен отправлять Access-токен в теле ответа (чтобы фронтенд мог хранить его в оперативной памяти приложения), а Refresh-токен — устанавливать исключительно через заголовок Set-Cookie с флагом HttpOnly.

    Проблема отзыва токенов (Revocation)

    Главный недостаток stateless архитектуры проявляется, когда нужно принудительно завершить сессию пользователя. Например, если пользователь нажал кнопку «Выйти со всех устройств» или администратор заблокировал аккаунт.

    Так как сервер не хранит сессии, он не может просто удалить токен. До истечения поля exp токен математически валиден. Для решения этой проблемы бэкенд-разработчики внедряют Deny List (черный список).

    При выходе из системы или блокировке, уникальный идентификатор токена (поле jti в полезной нагрузке) помещается в быстрое хранилище (например, Redis) с временем жизни (TTL), равным оставшемуся времени жизни самого токена.

    При каждом запросе бэкенд сначала проверяет криптографическую подпись JWT, а затем делает сверхбыстрый запрос в Redis: нет ли этого jti в черном списке? Если идентификатор найден, сервер возвращает ошибку 401 Unauthorized. Как только срок действия токена истекает естественным образом, Redis автоматически удаляет его из черного списка, освобождая память.

    Интеграция JWT требует глубокого понимания того, как браузеры обрабатывают данные и какие векторы атак существуют. Правильно настроенная связка короткоживущих токенов, защищенных cookie и системы черных списков в Redis позволяет создать масштабируемую и безопасную систему аутентификации, готовую к высоким нагрузкам.

    5. Реализация авторизации и делегирования доступа по протоколу OAuth2

    Реализация авторизации и делегирования доступа по протоколу OAuth2

    Представьте ситуацию: вы разрабатываете платформу для управления задачами. Чтобы пользователям было удобнее планировать свой день, вы решаете интегрировать приложение с Google Календарем. Для этого вашему бэкенду нужен доступ к расписанию пользователя. Самый очевидный, но катастрофически небезопасный путь — попросить пользователя ввести логин и пароль от его аккаунта Google прямо в вашей форме.

    Передача паролей сторонним приложениям нарушает базовые принципы информационной безопасности. Если вашу базу данных взломают, злоумышленники получат доступ не только к вашему сервису, но и к почте, документам и фотографиям пользователей. Для решения проблемы безопасного делегирования прав доступа был создан протокол OAuth2 (Open Authorization).

    Концепция ключа парковщика

    В основе OAuth2 лежит принцип, который в реальной жизни напоминает работу ключа парковщика (Valet Key). Когда вы отдаете машину сотруднику отеля, вы даете ему специальный ключ. Этот ключ позволяет завести двигатель и проехать пару километров до парковки, но им нельзя открыть багажник или бардачок.

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

    > OAuth 2.0 — это протокол авторизации, а не аутентификации. Он отвечает на вопрос «Имеет ли это приложение право прочитать контакты?», а не на вопрос «Кто этот пользователь?». Для аутентификации поверх OAuth2 используется надстройка OpenID Connect (OIDC).

    Роли в экосистеме OAuth2

    Для понимания механики работы протокола необходимо разобраться с четырьмя ключевыми ролями, которые участвуют в процессе обмена данными.

    Владелец ресурса (Resource Owner*): сам пользователь, которому принадлежат данные (например, владелец Google Календаря). Клиент (Client*): стороннее приложение, которое запрашивает доступ к данным. В контексте нашего курса это ваш Python-бэкенд. Сервер авторизации (Authorization Server*): сервер, который проверяет личность пользователя, спрашивает его согласие и выдает токены (например, сервер авторизации Google). Сервер ресурсов (Resource Server*): API, где физически хранятся данные пользователя и которое принимает выданные токены (например, Google Calendar API).

    | Характеристика | OAuth2 (Авторизация) | OpenID Connect (Аутентификация) | | :--- | :--- | :--- | | Главная цель | Делегирование прав доступа | Подтверждение личности пользователя | | Артефакт | Access Token (обычно непрозрачный) | ID Token (всегда в формате JWT) | | Что содержит | Разрешения (Scopes) | Данные профиля (имя, email, аватар) | | Аналогия | Пропуск в конкретный кабинет | Паспорт гражданина |

    Поток Authorization Code

    Протокол OAuth2 описывает несколько сценариев (грантов) получения токена. Для веб-приложений с собственным бэкендом стандартом безопасности является Authorization Code Flow (поток с кодом авторизации). Его главное преимущество в том, что итоговый токен доступа никогда не попадает в браузер пользователя, а передается напрямую между серверами.

    Разберем этот процесс по шагам:

  • Пользователь нажимает кнопку «Импортировать из Google» в вашем приложении.
  • Ваш бэкенд формирует специальную ссылку и перенаправляет браузер пользователя на Сервер авторизации. В ссылке указывается идентификатор вашего приложения (client_id) и список запрашиваемых прав (scope).
  • Пользователь видит страницу Google, авторизуется там (если еще не) и нажимает «Разрешить».
  • Сервер авторизации перенаправляет браузер обратно на ваш бэкенд (на заранее зарегистрированный redirect_uri), добавляя в URL короткоживущий одноразовый код авторизации.
  • Ваш бэкенд берет этот код, добавляет к нему свой секретный ключ (client_secret) и делает прямой POST-запрос к Серверу авторизации.
  • Сервер авторизации проверяет код и секрет, после чего возвращает бэкенду Access Token (и, опционально, Refresh Token).
  • Пример реализации пятого шага на Python с использованием библиотеки requests:

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

    Защита от подделки запросов: параметр State

    Одной из уязвимостей классического OAuth2 является атака CSRF (подделка межсайтовых запросов) на процесс привязки аккаунта. Злоумышленник может начать процесс авторизации под своим аккаунтом, получить код авторизации, но не отправлять его на ваш бэкенд. Вместо этого он подкидывает ссылку с этим кодом жертве.

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

    Для защиты используется параметр state. Перед перенаправлением пользователя на Сервер авторизации, ваш бэкенд генерирует случайную криптографическую строку, сохраняет ее в сессии пользователя (или в зашифрованной cookie) и добавляет в URL авторизации.

    Когда Сервер авторизации возвращает пользователя обратно, он возвращает и этот параметр state. Бэкенд обязан сравнить полученный state с тем, что хранится в сессии. Если они не совпадают, процесс немедленно прерывается.

    Эволюция безопасности: PKCE

    В мобильных и одностраничных приложениях (SPA), где нет классического бэкенда для безопасного хранения client_secret, используется расширение PKCE (Proof Key for Code Exchange). Сегодня лучшие практики ИБ требуют использовать PKCE даже для классических бэкенд-приложений.

    Механика PKCE базируется на криптографическом хешировании. Перед началом авторизации клиент генерирует случайную строку — . Затем клиент вычисляет от нее хеш по алгоритму SHA-256. Назовем результат :

    Клиент отправляет code\_verifiercode\_challenge$, который он сохранил ранее. Если значения совпадают, сервер убеждается, что токен запрашивает именно тот клиент, который начинал сессию.

    Использование OAuth2 с потоком Authorization Code, защитой через параметр state и расширением PKCE обеспечивает надежный фундамент для интеграции вашего бэкенда с внешними сервисами, минимизируя риски компрометации пользовательских данных.