Я пытался решить аналогичную проблему. Мои пользователи должны проходить аутентификацию для каждого запроса, который они делают. Я сосредоточился на том, чтобы аутентифицировать пользователей хотя бы один раз с помощью бэкэнд-приложения (проверка токена 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 для моего приложения:
# Cache for internal auth checks
proxy_cache_path /usr/local/var/nginx/cache/auth levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=10m use_temp_path=off;
# Cache for content
proxy_cache_path /usr/local/var/nginx/cache/resx levels=1:2 keys_zone=content_cache:16m max_size=128m inactive=5m use_temp_path=off;
server {
listen 443 ssl http2;
server_name ........;
include /usr/local/etc/nginx/include-auth-internal.conf;
location /api/v1 {
# Auth magic happens here
auth_request /auth;
auth_request_set $user $upstream_http_X_User_Id;
auth_request_set $customer $upstream_http_X_Customer_Id;
auth_request_set $permissions $upstream_http_X_Permissions;
# The backend app, once Nginx has performed internal auth.
proxy_pass http://127.0.0.1:5000;
proxy_set_header X-User-Id $user;
proxy_set_header X-Customer-Id $customer;
proxy_set_header X-Permissions $permissions;
# Cache content
proxy_cache content_cache;
proxy_cache_key "$request_method-$request_uri";
}
location /api/v1/Logout {
auth_request /auth/logout;
}
}
Теперь вот экстракт конфигурации для внутренней /auth
конечной точки, включенный выше как /usr/local/etc/nginx/include-auth-internal.conf
:
# Called before every request to backend
location = /auth {
internal;
proxy_cache auth_cache;
proxy_cache_methods GET HEAD POST;
proxy_cache_key "$cookie_token";
# Valid tokens cache duration is set by backend returning a properly set Cache-Control header
# Invalid tokens are shortly cached to protect backend but not flood Nginx cache
proxy_cache_valid 401 30s;
# Valid tokens are cached for 5 minutes so we can get the backend to re-validate them from time to time
proxy_cache_valid 200 5m;
proxy_pass http://127.0.0.1:1234/auth/_Internal;
proxy_set_header Host ........;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Accept application/json;
}
# To invalidate a not expired token, use a specific backend endpoint.
# Then we cache the token invalid/401 response itself.
location = /auth/logout {
internal;
proxy_cache auth_cache;
proxy_cache_key "$cookie_token";
# Proper caching duration (> token expire date) set by backend, which will override below default duration
proxy_cache_valid 401 30m;
# A Logout requests forces a cache refresh in order to store a 401 where there was previously a valid authorization
proxy_cache_bypass 1;
# This backend endpoint always returns 401, with a cache header set to the expire date of the token
proxy_pass http://127.0.0.1:1234/auth/_Internal/Logout;
proxy_set_header Host ........;
proxy_pass_request_body off;
}
,
Адресация содержания контента
Теперь аутентификация отделена от данных. Поскольку вы сказали, что он идентичен для каждого пользователя, сам контент также может кэшироваться Nginx (в моем примере, в content_cache
зоне).
Масштабируемость
Этот сценарий прекрасно работает из коробки, если у вас есть один сервер Nginx. В реальном сценарии вы, вероятно, обладаете высокой доступностью, то есть несколькими экземплярами Nginx, потенциально также размещая ваше (Laravel) внутреннее приложение. В этом случае любой запрос, сделанный вашими пользователями, может быть отправлен на любой из ваших серверов Nginx, и пока все они локально не кэшируют токен, они будут продолжать обращаться к вашему бэкэнду, чтобы проверить его. Для небольшого количества серверов использование этого решения все равно принесет большие выгоды.
Однако важно отметить, что с несколькими серверами Nginx (и, следовательно, кешами) вы теряете возможность выхода из системы на стороне сервера, потому что вы не можете очистить (путем принудительного обновления) кеш токенов на всех из них, например, /auth/logout
делает в моем примере. У вас останется только 5-минутная длительность кэша токена, которая вскоре заставит ваш бэкэнд запросить и сообщит Nginx, что запрос отклонен. Частичным обходным решением является удаление заголовка токена или файла cookie на клиенте при выходе из системы.
Любой комментарий будет очень приветствоваться и ценится!