Я работаю над веб-приложением, которое должно иметь дело с очень большими импульсами одновременных пользователей, которым требуется авторизация, для запроса идентичного контента. В своем нынешнем состоянии он полностью наносит ущерб даже 32-ядерному экземпляру AWS.
(Обратите внимание, что мы используем Nginx в качестве обратного прокси)
Ответ не может быть просто кэширован, так как в худшем случае мы должны проверить, аутентифицирован ли пользователь путем декодирования его JWT. Это требует от нас запуска Laravel 4, который, по мнению большинства, идет медленно , даже с включенными PHP-FPM и OpCache. Это в основном из-за здоровенной фазы начальной загрузки.
Кто-то может задать вопрос: «Почему вы в первую очередь использовали PHP и Laravel, если знали, что это будет проблемой?» - но уже слишком поздно возвращаться к этому решению!
Возможное решение
Одно решение, которое было предложено, состоит в том, чтобы извлечь модуль Auth из Laravel в легкий внешний модуль (написанный на чем-то быстром, как C), чья ответственность заключается в том, чтобы декодировать JWT и решить, аутентифицирован ли пользователь.
Поток запроса будет:
- Проверьте, не попал ли в кеш (если не перешел на PHP как обычно)
- Расшифровать токен
- Проверьте, действительно ли это
- Если допустимо , подавать из кеша
- Если он недействителен , сообщите Nginx, а затем Nginx передаст запрос в PHP для обработки в обычном режиме.
Это позволит нам не использовать PHP после того, как мы передадим этот запрос одному пользователю, и вместо этого обратиться к легковесному модулю, чтобы возиться с декодированием JWT и любыми другими предупреждениями, которые идут с этим типом аутентификации.
Я даже думал написать этот код напрямую как модуль расширения Nginx HTTP.
Обеспокоенность
Меня беспокоит то, что я никогда не видел, чтобы это было сделано раньше, и задавался вопросом, есть ли лучший способ.
Кроме того, когда вы добавляете какой-либо пользовательский контент на страницу, этот метод полностью убивает.
Есть ли другое простое решение, доступное непосредственно в Nginx? Или нам нужно использовать что-то более специализированное, например, Varnish?
Мои вопросы:
Имеет ли смысл приведенное выше решение?
Как это обычно достигается?
Есть ли лучший способ добиться такого же или лучшего прироста производительности?
источник
Ответы:
Я пытался решить аналогичную проблему. Мои пользователи должны проходить аутентификацию для каждого запроса, который они делают. Я сосредоточился на том, чтобы аутентифицировать пользователей хотя бы один раз с помощью бэкэнд-приложения (проверка токена JWT), но после этого я решил, что мне больше не нужен бэкэнд.
Я решил не требовать плагин Nginx, который не включен по умолчанию. В противном случае вы можете проверить скрипты nginx-jwt или Lua, и это, вероятно, будет отличным решением.
Адресация аутентификации
До сих пор я сделал следующее:
Делегировал аутентификацию Nginx, используя
auth_request
. Это вызываетinternal
местоположение, которое передает запрос к моей конечной точке проверки токена бэкэнда. Уже одно это не решает проблему обработки большого количества проверок.Результат проверки токена кэшируется с помощью
proxy_cache_key "$cookie_token";
директивы. После успешной проверки токена серверная часть добавляетCache-Control
директиву, которая указывает Nginx кэшировать токен только на срок до 5 минут. На этом этапе любой токен аутентификации, проверенный один раз, находится в кэше, последующие запросы от того же пользователя / токена больше не затрагивают бэкэнд аутентификации!Чтобы защитить мое внутреннее приложение от возможного переполнения недействительными токенами, я также кэшировал отклоненные проверки, когда моя конечная точка внутреннего сервера возвращает 401. Эти кэшируются только на короткое время, чтобы избежать потенциального заполнения кеша Nginx такими запросами.
Я добавил несколько дополнительных улучшений, таких как конечная точка выхода из системы, которая делает недействительным токен, возвращая 401 (который также кэшируется Nginx), так что если пользователь нажимает кнопку выхода из системы, токен больше не может использоваться, даже если срок его действия не истек.
Кроме того, мой кеш Nginx содержит для каждого токена связанный пользователь в виде объекта JSON, что избавляет меня от необходимости извлекать его из БД, если мне нужна эта информация; а также спасает меня от расшифровки токена.
О времени жизни токенов и обновления токенов
Через 5 минут токен истечет в кеше, поэтому бэкэнд снова будет запрошен. Это необходимо для того, чтобы вы могли сделать недействительным токен, потому что пользователь вышел из системы, потому что он был взломан и так далее. Такая периодическая повторная проверка с надлежащей реализацией в бэкэнде избавляет меня от необходимости использовать токены обновления.
Традиционно токены обновления будут использоваться для запроса нового токена доступа; они будут храниться в вашем бэкэнде, и вы проверите, что запрос на токен доступа сделан с токеном обновления, который совпадает с тем, который у вас есть в базе данных для этого конкретного пользователя. Если пользователь выходит из системы или токены скомпрометированы, вы должны удалить / аннулировать токен обновления в вашей БД, чтобы следующий запрос на новый токен с использованием недействительного токена обновления завершился неудачно.
Короче говоря, токены обновления обычно имеют длительный срок действия и всегда сверяются с бэкэндом. Они используются для генерации токенов доступа, которые имеют очень короткий срок действия (несколько минут). Эти токены доступа обычно достигают вашего бэкэнда, но вы проверяете только их подпись и дату окончания срока действия.
Здесь, в моей настройке, мы используем токены с более длительным сроком действия (могут быть часы или день), которые имеют ту же роль и функции, что и токен доступа и токен обновления. Поскольку их валидация и аннулирование кэшируются Nginx, они полностью проверяются бэкэндом раз в 5 минут. Таким образом, мы сохраняем преимущество использования токенов обновления (чтобы иметь возможность быстро аннулировать токен) без дополнительной сложности. И простая проверка никогда не достигает вашего бэкэнда, который по крайней мере на 1 порядок медленнее, чем кэш Nginx, даже если он используется только для проверки подписи и даты истечения срока действия.
С помощью этой настройки я мог отключить аутентификацию в своем бэкэнде, так как все входящие запросы достигают
auth_request
директивы Nginx, прежде чем ее трогать.Это не решает проблему полностью, если вам нужно выполнить какую-либо авторизацию для каждого ресурса, но, по крайней мере, вы сохранили основную часть авторизации. И вы даже можете избежать расшифровки токена или выполнить поиск в БД для доступа к данным токена, поскольку кэшированный ответ аутентификации Nginx может содержать данные и передавать их обратно бэкэнду.
Сейчас моя самая большая проблема в том, что я могу нарушить что-то очевидное, связанное с безопасностью, не осознавая этого. При этом любой полученный токен по-прежнему проверяется, по крайней мере, один раз, прежде чем будет кэширован Nginx. Любой закаленный токен будет отличаться, поэтому не попадет в кеш, так как ключ кеша также будет другим.
Также, возможно, стоит упомянуть, что аутентификация в реальном мире будет бороться с кражей токенов, генерируя (и проверяя) дополнительный одноразовый номер или что-то в этом роде.
Вот упрощенный фрагмент моей конфигурации Nginx для моего приложения:
Теперь вот экстракт конфигурации для внутренней
/auth
конечной точки, включенный выше как/usr/local/etc/nginx/include-auth-internal.conf
:,
Адресация содержания контента
Теперь аутентификация отделена от данных. Поскольку вы сказали, что он идентичен для каждого пользователя, сам контент также может кэшироваться Nginx (в моем примере, в
content_cache
зоне).Масштабируемость
Этот сценарий прекрасно работает из коробки, если у вас есть один сервер Nginx. В реальном сценарии вы, вероятно, обладаете высокой доступностью, то есть несколькими экземплярами Nginx, потенциально также размещая ваше (Laravel) внутреннее приложение. В этом случае любой запрос, сделанный вашими пользователями, может быть отправлен на любой из ваших серверов Nginx, и пока все они локально не кэшируют токен, они будут продолжать обращаться к вашему бэкэнду, чтобы проверить его. Для небольшого количества серверов использование этого решения все равно принесет большие выгоды.
Однако важно отметить, что с несколькими серверами Nginx (и, следовательно, кешами) вы теряете возможность выхода из системы на стороне сервера, потому что вы не можете очистить (путем принудительного обновления) кеш токенов на всех из них, например,
/auth/logout
делает в моем примере. У вас останется только 5-минутная длительность кэша токена, которая вскоре заставит ваш бэкэнд запросить и сообщит Nginx, что запрос отклонен. Частичным обходным решением является удаление заголовка токена или файла cookie на клиенте при выходе из системы.Любой комментарий будет очень приветствоваться и ценится!
источник