Кешування аутентифікованих запитів для всіх користувачів


9

Я працюю над веб-додатком, який повинен мати справу з дуже великими імпульсами одночасних користувачів, яким потрібно мати дозвіл, запитувати однаковий вміст. У своєму нинішньому стані це абсолютно калічить навіть 32-ядерний екземпляр AWS.

(Зверніть увагу, що ми використовуємо Nginx як зворотний проксі)

Відповідь не може бути просто кешована, оскільки, в гіршому випадку, ми повинні перевірити, чи користувач аутентифікований, розшифрувавши його JWT. Це вимагає, щоб ми запускали Laravel 4, що, погоджуючись більшість, повільно , навіть із включеними PHP-FPM та OpCache. В основному це пов'язано з здоровенною фазою завантаження.

Можна задати питання "Чому ви використовували PHP та Laravel в першу чергу, якщо ви знали, що це буде проблемою?" - але вже пізно повертатися до цього рішення!

Можливе рішення

Одне з запропонованих рішень - це витягнути модуль Auth з Laravel до легкого зовнішнього модуля (написаного так швидко, як C), відповідальність за який - декодувати JWT та вирішити, чи буде користувач автентифікований.

Потік запиту буде:

  1. Перевірте, чи потрапило кеш-пам'ять (якщо не переходити до PHP як звичайно)
  2. Розшифруйте маркер
  3. Перевірте, чи дійсна вона
  4. Якщо дійсно , подайте з кеша
  5. Якщо недійсний , скажіть Nginx, і тоді Nginx передасть запит PHP, щоб мати справу як звичайне.

Це дозволить нам не потрапляти на PHP, як тільки ми надіслали цей запит одному користувачеві, а натомість звернемося до легкого модуля, щоб зіпсуватись з розшифровкою JWT та будь-якими іншими застереженнями, які поставляються з таким типом автентичності.

Я навіть думав написати цей код безпосередньо як модуль розширення Nginx HTTP.

Побоювання

Мене хвилює те, що я ніколи не бачив цього робити раніше і запитав, чи є кращий спосіб.

Крім того, що друге, коли ви додасте на сторінку будь-який вміст, визначений для користувача, це повністю знищує цей метод.

Чи є ще одне простіше рішення, доступне безпосередньо в Nginx? Або нам би довелося використовувати щось більш спеціалізоване, як лак?

Мої запитання:

Чи має вищезгадане рішення сенс?

Як це нормально підходить?

Чи є кращий спосіб досягти подібного або кращого підвищення продуктивності?


Я стикаюся з подібною проблемою. Кілька ідей: a) Nginx auth_request може передати вашій мікросервісі аутентифікації, що полегшує необхідність розробки модуля Nginx. b) Крім того, ваша мікросервіс може перенаправляти аутентифікованих користувачів до тимчасової URL-адреси, яка є загальнодоступною, кешованою і непридатною для користування, але може бути перевірена серверною програмою PHP, щоб вона була дійсною протягом обмеженого періоду (період кешу). Це приносить певну безпеку, якщо тимчасова URL-адреса просочена до ненадійного користувача, вони можуть отримати доступ до вмісту протягом обмеженого періоду, як і маркер OAuth Bearer.
Джеймс

Ви придумали рішення цього? Я зіткнувся з тим же самим
тимбродером

Виявляється, маючи великий кластер оптимізованих задніх вузлів, ми змогли впоратися з навантаженням, але я впевнений, що цей підхід є великим рішенням економії витрат на довгостроковий період. Якщо ви знаєте деякі відповіді, які ви могли б подавати заздалегідь, якщо ви розігрієте кеш перед напливом запитів, економія резервних ресурсів та підвищення надійності буде дуже високим.
iamyojimbo

Відповіді:


9

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

Будь-який коментар буде дуже вдячний і вдячний!


Вам слід отримати набагато більше результатів! Дуже корисно, дякую!
Гершон Папі

"Я додав декілька додаткових удосконалень, таких як кінцева точка виходу, що визнає недійсним маркер, повернувши 401 (який також кеширується Nginx), так що якщо користувач натискає вихід, токен не можна використовувати більше, навіть якщо його термін дії не закінчився. " - Це розумно! , але чи ви насправді в чорному списку маркерів також у своєму бекенді, так що у випадку, якщо кеш-пам'ять впаде або щось подібне, користувач все ще не може увійти в систему за допомогою цього конкретного маркера?
gaurav5430

"Однак важливо відзначити, що за допомогою декількох серверів Nginx (і, таким чином, кеш-пам'яті) ви втрачаєте можливість виходу на сторону сервера, оскільки ви не можете очистити (примушуючи оновити) кеш-кодів жетонів на всіх них, як / auth / logout у моєму прикладі. " чи можете ви докладно?
gaurav5430
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.