Próbowałem rozwiązać podobny problem. Moi użytkownicy muszą być uwierzytelniani przy każdym zgłoszonym przez siebie żądaniu. Koncentrowałem się na uwierzytelnieniu użytkowników przynajmniej raz przez aplikację backendu (sprawdzanie poprawności tokena JWT), ale potem zdecydowałem, że nie będę już potrzebował backendu.
Zdecydowałem się uniknąć wymagania jakiejkolwiek wtyczki Nginx, która nie jest domyślnie dołączona. W przeciwnym razie możesz sprawdzić skrypty nginx-jwt lub Lua i prawdopodobnie byłyby to świetne rozwiązania.
Uwierzytelnianie adresowania
Do tej pory zrobiłem następujące:
Delegowanie uwierzytelnienia do Nginx przy użyciu auth_request
. To wywołuje internal
lokalizację, która przekazuje żądanie do mojego punktu końcowego sprawdzania poprawności tokena. Samo to w ogóle nie rozwiązuje problemu obsługi dużej liczby walidacji.
Wynik sprawdzania poprawności tokena jest buforowany przy użyciu proxy_cache_key "$cookie_token";
dyrektywy. Po pomyślnym sprawdzeniu poprawności tokena, backend dodaje Cache-Control
dyrektywę, która mówi Nginxowi, aby buforował token tylko do 5 minut. W tym momencie każdy zatwierdzony token uwierzytelnienia znajduje się w pamięci podręcznej, kolejne żądania od tego samego użytkownika / tokena nie dotykają już zaplecza uwierzytelniania!
Aby zabezpieczyć moją aplikację backend przed potencjalnym zalaniem przez nieprawidłowe tokeny, buforowałem również odrzucone walidacje, gdy mój punkt końcowy backend zwraca 401. Te są buforowane tylko przez krótki czas, aby uniknąć potencjalnego zapełnienia pamięci podręcznej Nginx takimi żądaniami.
Dodałem kilka dodatkowych ulepszeń, takich jak punkt końcowy wylogowania, który unieważnia token, zwracając 401 (który jest również buforowany przez Nginx), tak że jeśli użytkownik kliknie wylogowanie, token nie może być już używany, nawet jeśli nie wygasł.
Ponadto moja pamięć podręczna Nginx zawiera dla każdego tokena powiązanego użytkownika jako obiekt JSON, co chroni mnie przed pobraniem go z bazy danych, jeśli potrzebuję tych informacji; a także ratuje mnie przed odszyfrowaniem tokena.
Informacje o czasie życia tokenu i odświeżaniu tokenów
Po 5 minutach token wygaśnie w pamięci podręcznej, więc backend zostanie ponownie zapytany. Ma to na celu zapewnienie, że możesz unieważnić token, ponieważ użytkownik wylogowuje się, ponieważ został przejęty i tak dalej. Takie okresowe przedłużanie ważności, z odpowiednią implementacją w backend, pozwala mi uniknąć używania tokenów odświeżania.
Tradycyjnie tokeny odświeżające byłyby używane do żądania nowego tokena dostępu; będą one przechowywane w wewnętrznej bazie danych, a użytkownik zweryfikuje, czy prośba o token dostępu została wysłana za pomocą tokenu odświeżania pasującego do tego, który użytkownik ma w bazie danych. Jeśli użytkownik wyloguje się lub tokeny zostaną naruszone, usuniesz / unieważnisz token odświeżania w swojej bazie danych, tak aby następne żądanie nowego tokena przy użyciu unieważnionego tokenu odświeżania zakończyło się niepowodzeniem.
Krótko mówiąc, tokeny odświeżania zwykle mają długi okres ważności i zawsze są sprawdzane względem wewnętrznej bazy danych. Służą do generowania tokenów dostępu, które mają bardzo krótki okres ważności (kilka minut). Te tokeny dostępu zwykle docierają do twojego zaplecza, ale sprawdzasz tylko ich podpis i datę ważności.
W moim ustawieniu używamy tokenów o dłuższym okresie ważności (może to być godziny lub dzień), które pełnią tę samą rolę i funkcje co token dostępu i token odświeżania. Ponieważ mamy buforowanie ich sprawdzania poprawności i unieważnienia przez Nginx, są one w pełni weryfikowane przez backend tylko raz na 5 minut. Zachowujemy więc korzyść z używania tokenów odświeżania (jesteśmy w stanie szybko unieważnić token) bez dodatkowej złożoności. A prosta weryfikacja nigdy nie dociera do backendu, który jest co najmniej o 1 rząd wielkości wolniejszy niż pamięć podręczna Nginx, nawet jeśli jest używany tylko do sprawdzania podpisu i daty ważności.
Dzięki tej konfiguracji mogę wyłączyć uwierzytelnianie w moim backendie, ponieważ wszystkie przychodzące żądania docierają do auth_request
dyrektywy Nginx przed jej dotknięciem.
Nie rozwiązuje to w pełni problemu, jeśli musisz wykonać dowolną autoryzację dla jednego zasobu, ale przynajmniej zapisałeś podstawową część autoryzacji. I możesz nawet uniknąć odszyfrowania tokena lub przeszukać bazę danych, aby uzyskać dostęp do danych tokenu, ponieważ buforowana odpowiedź autoryzacji Nginx może zawierać dane i przekazywać je z powrotem do backendu.
Teraz moim największym zmartwieniem jest to, że mogę nie wiedzieć o czymś oczywistym związanym z bezpieczeństwem. To powiedziawszy, każdy otrzymany token jest sprawdzany przynajmniej raz, zanim zostanie zbuforowany przez Nginx. Każdy temperowany token byłby inny, więc nie trafiałby do pamięci podręcznej, ponieważ klucz pamięci podręcznej również byłby inny.
Być może warto wspomnieć, że autentyczne uwierzytelnianie w świecie walczyłoby z kradzieżą tokenów poprzez generowanie (i weryfikację) dodatkowej wartości jednorazowej lub czegoś takiego.
Oto uproszczony fragment mojej konfiguracji Nginx dla mojej aplikacji:
# 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;
}
}
Oto wyciąg z konfiguracji dla wewnętrznego /auth
punktu końcowego, uwzględniony powyżej jako /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;
}
.
Adresowanie treści wyświetlanych
Teraz uwierzytelnianie jest oddzielone od danych. Ponieważ powiedziałeś, że jest identyczny dla każdego użytkownika, sama zawartość może być buforowana przez Nginx (w moim przykładzie w content_cache
strefie).
Skalowalność
Ten scenariusz działa świetnie od razu po założeniu, że masz jeden serwer Nginx. W scenariuszu ze świata rzeczywistego zapewne masz wysoką dostępność, co oznacza wiele instancji Nginx, potencjalnie również hostując twoją aplikację (Laravel). W takim przypadku każde żądanie wysłane przez użytkowników może zostać wysłane na dowolny z serwerów Nginx i dopóki wszyscy lokalnie nie zbuforują tokena, będą docierać do backendu, aby go zweryfikować. W przypadku niewielkiej liczby serwerów korzystanie z tego rozwiązania nadal przyniosłoby duże korzyści.
Należy jednak pamiętać, że w przypadku wielu serwerów Nginx (a tym samym pamięci podręcznych) tracisz możliwość wylogowania po stronie serwera, ponieważ nie możesz wyczyścić (wymuszając odświeżenie) pamięci podręcznej tokenów na wszystkich z nich, takich jak /auth/logout
robi w moim przykładzie. Pozostało ci jedynie 5 milionów czasu trwania pamięci podręcznej tokenów, co zmusi twoje zapytanie do szybkiego zapytania i poinformuje Nginx, że żądanie zostało odrzucone. Częściowym obejściem jest usunięcie nagłówka tokena lub pliku cookie na kliencie podczas wylogowywania.
Wszelkie komentarze będą mile widziane i mile widziane!