Pracuję nad aplikacją internetową, która musi radzić sobie z bardzo dużymi impulsami równoczesnych użytkowników, którzy muszą być upoważnieni do żądania identycznych treści. W obecnym stanie jest całkowicie paraliżujący nawet dla 32-rdzeniowej instancji AWS.
(Pamiętaj, że używamy Nginx jako odwrotnego proxy)
Odpowiedzi nie można po prostu zapisać w pamięci podręcznej, ponieważ w najgorszym przypadku musimy sprawdzić, czy użytkownik jest uwierzytelniony poprzez dekodowanie JWT. To wymaga od nas uruchomienia Laravela 4, co większość zgodziłaby się, jest powolna , nawet przy włączonym PHP-FPM i OpCache. Wynika to głównie z dużej fazy ładowania.
Można by zadać pytanie „Dlaczego używałeś PHP i Laravela, jeśli wiedziałeś, że to będzie problem?” - ale jest już za późno, aby wrócić do tej decyzji!
Możliwe rozwiązanie
Jednym z zaproponowanych rozwiązań jest wypakowanie modułu Auth z Laravela do lekkiego modułu zewnętrznego (napisanego w czymś szybkim jak C), którego zadaniem jest zdekodowanie JWT i decyzja, czy użytkownik jest uwierzytelniony.
Przepływ żądania byłby następujący:
- Sprawdź, czy pamięć podręczna nie trafiła (jeśli nie przechodzi normalnie do PHP)
- Dekoduj token
- Sprawdź, czy jest poprawny
- Jeśli jest poprawny , podawaj z pamięci podręcznej
- Jeśli jest niepoprawny , powiedz Nginx, a wtedy Nginx przekaże żądanie do PHP, aby postępować normalnie.
Pozwoli nam to nie trafić do PHP po tym, jak doręczymy tę prośbę jednemu użytkownikowi, i zamiast tego sięgniemy do lekkiego modułu, aby zadzierać z dekodowaniem JWT i wszelkich innych zastrzeżeń, które są dostarczane z tym rodzajem autoryzacji.
Myślałem nawet o napisaniu tego kodu bezpośrednio jako modułu rozszerzenia HTTP Nginx.
Obawy
Obawiam się, że nigdy wcześniej tego nie widziałem i zastanawiałem się, czy jest lepszy sposób.
Ponadto, po dodaniu do strony treści specyficznych dla użytkownika, całkowicie zabija tę metodę.
Czy istnieje inne prostsze rozwiązanie dostępne bezpośrednio w Nginx? Czy też musielibyśmy użyć czegoś bardziej specjalistycznego, takiego jak Lakier?
Moje pytania:
Czy powyższe rozwiązanie ma sens?
Jak do tego zwykle podejść?
Czy istnieje lepszy sposób na osiągnięcie podobnego lub lepszego wzrostu wydajności?
źródło
Odpowiedzi:
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łujeinternal
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 dodajeCache-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:
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
:.
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!
źródło