Я намагався вирішити подібне питання. Мої користувачі повинні мати автентифікацію для кожного запиту, який вони роблять. Я зосереджувався на тому, щоб принаймні один раз засвідчити автентифікацію користувачів за допомогою додатка програми (перевірка токена 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 кешується. Будь-який загартований маркер був би різним, тому не потрапляв би в кеш, оскільки ключ кешу також був би іншим.
Крім того, можливо, варто згадати, що справжня автентифікація світу боролася б проти крадіжок жетонів, генеруючи (і перевіряючи) додаткову Nonce чи щось таке.
Ось спрощений витяг мого конфігурації 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 на клієнті під час виходу з системи.
Будь-який коментар буде дуже вдячний і вдячний!