Przełączanie użytkownika z generowaniem JWT w Spring Security

0

Chodzi o mechanizm przełączania kont użytkowników podobny jak w Google, YouTube...

Architektura:

  • serwer autentykacyjny z użyciem Spring Security
  • inne mikroserwisy (dostęp tylko z tokenem JWT)
  • load balancer weryfikujący token JWT
  • aplikacja webowa

Działa to tak, że użytkownik loguje się w aplikacji webowej i serwer autentykacyjny generuje token JWT.

POST /token?username=user&password=pass&grant_type=password&scope=read

Oprócz tego wysyłany jest nagłówek Authorization typu basic, gdzie uwierzytelniamy aplikację kliencką.

Authorization: Basic ...........

W Spring Security dużo rzeczy dzieje się automatycznie. Jeśli nie ma sesji zalogowanego użytkownika, to endpoint /token działa, weryfikuje użytkownika i zwraca ważny token JWT. Ale teraz chcemy przełączyć się na inne konto.

Dane wejściowe, jakimi dysponujemy:

  • token JWT zalogowanego użytkownika
  • ID użytkownika, na jakiego chcemy przełączyć (będzie weryfikacja, czy to user powiązany)
  • nie chcę w aplikacji webowej przechowywać wpisanego loginu i hasła (kwestie bezpieczeństwa)

Dawniej Spring Security posiadał możliwość wielu sesji, ale już nie ma. Jest funkcja przełączenia się na dowolnego użytkownika (bez weryfikacji), jednak to odpada, bo musimy weryfikować, na kogo.

Najlepiej chyba byłoby to zrobić tak, że tworzymy endpoint np. /switch i wysyłamy nagłówek Authorization: Bearer z tokenem JWT. Problem w tym, że wtedy Spring Security tego nie rozumie i wysyła odpowiedź Unauthenticated. Być może trzeba napisać filtr lub wykorzystać istniejący, żeby dla tego konkretnego endpointu był sprawdzany token JWT, a potem go ręcznie generować. Ktoś wie, jak to ugryźć i czy Spring Security ma coś takiego?

0

Ja chyba nie całkiem rozumiem co chcesz zrobić. Chcesz wysłać request zawierający JWT i wygenerować nowe JWT w odpowiedzi?

0

@Shalom: Tak. Chcę wysłać request zawierający JWT i wygenerować w odpowiedzi nowe JWT (dla innego konta, które należy do tego samego właściciela).

0

Nie wiem czy tak to ma wyglądać, ale klikasz sobie załóżmy w swój awatar i wyświetlają ci się powiązane konta. Klikając na dane konto w celu przełączenia, leci ci na jakiś endpoint do przełączania konta. Wymagane jest, aby endpoint był dostępny dla zalogowanych użytkowników, nie ważne z jakimi uprawnieniami. Sprawdza aktualny token + przesłane informacje na jakie konto użytkownik chce się przełączyć. Jeżeli konto jest powiązane to tworzy nowy token i odsyła użytkownikowi. Nie potrzebujesz hasła, ani nic, gdyż masz uniwersalne hasło do tworzenia tokenów.

Drugą opcją, aby w locie to było robione, to użycie filtra. Tutaj jednak zależy co chciałbyś osiągnąć. Jeżeli dane usługi dostępne są tylko dla osób z odpowiednimi uprawnieniami, to byłoby automatycznie przełączenie na powiązane konto. Jednak pomysł z endpointem bardziej mi się podoba.

Nie wiem czy o to ci chodziło

0

Pierwszy pomysł. Tylko czy da się to zrobić tak, jak bym chciał?

Jeśli w nagłówku Authorization zamiast uwierzytelnienia klienta (Basic authentication ze sztywnym loginem i hasłem) pójdzie token JWT zalogowanego użytkownika, to Spring Security z automatu odeśle WWW-Authenticate i przeglądarka wyświetli monit o login i hasło (nie użytkownika, tylko klienta, czyli aplikacji webowej). Pewnie da się to ominąć filtrami, czyli że dla konkretnego endpointu stosujemy inny filtr, który z nagłówka Authorization odczytuje token JWT użytkownika albo nie sprawdza tego nagłówka wcale i trzeba to ręcznie zrobić (sparsować token, zweryfikować użytkownika i wygenerować nowy token). Inny sposób to przekazać JWT jako parametr query string i też ręcznie robić weryfikację. Aż dziwne, że Spring Security nie ma tego out of box.

Jeśli znacie jakieś przykłady w istniejących aplikacjach, jak sensownie do tego podejść, to podeślijcie.

Druga kwestia to "zapamiętaj mnie". Jeśli generujemy JWT na podstawie loginu i hasła, to nie a się w ogóle generować refresh tokenów - jest to zablokowane w Springu, rzekomo tak nakazuje specyfikacja OAuth. Dlaczego tak jest?

0

Jeżeli twoja aplikacja to rest, czyli udostępnia zasoby i przyjmuje (jest bezstanowa), to nie powinna się opierać tylko na jwt, a nie remember me?

0

@brus97: tak, opiera się na REST, ale token JWT ma datę ważności i bez odświeżenia (np. refresh tokenem, który nie przychodzi) token JWT traci ważność po pewnym czasie. Oczywiście można ustawić datę ważności na bardzo odległą, tylko na ile to bezpieczne? Lepiej chyba odświeżać go co jakiś czas (czyli generować nowy).

A problem z przełączaniem da się łatwo obejść, trzymając login i hasło w aplikacji (np. w sessionStorage) i przy przełączaniu użytkownika po prostu logować się tymi samymi danymi, ale dodatkowo przekazując ID innego konta. I wtedy nie trzeba by było pisać dodatkowych filtrów, jeśli Spring Security czegoś takiego nie posiada. Tylko jest tu kwestia bezpieczeństwa, żeby trzymać wrażliwe dane w pamięci.

0

Nie nie, przy rest nie trzymasz żadnych danych logowania w aplikacji. Żeby wykonać jwt w springu (takim najprostszym sposobem), implementujesz mechanizm logowania jaki chcesz (możesz przy pomocy UserDetails i UserDetailsService) na endpoint /login. Przeprowadza on autentykacje i zwraca jwt, po czym każdy request ma dołączony ten jwt i przechodzi on przez filtr, który sam definiujesz. Spring nie ma wbudowanego security na podstawie jwt (z tego co mi wiadomo). Jest wiele bibliotek, które pomagają to obsłużyć. Żeby przelogować użytkownika, robisz sobie osobny endpoint. Dla refresh tokenu też osobny. Jest taka metoda dla HttpSecurity, addFilter(...). Dzięki niej możesz ustalić co ma się przed danym filtrem wykonać, lub po.

Aplikacja webowa jeżeli dostaje exception unauthorized, sprawdza czy ma refresh token i wysyła go na np. /refresh. Wtedy następuje wygenerowanie nowego. Pamiętaj, że tylko prawdziwy token, ma gwarantować dostęp do zasobów, dlatego ma on krótki czas.

0

@brus97: Tak, opiera się na JWT (który zawiera ID użytkownika i datę ważności). Kwestia zapamiętywania - z tego, co wiem, to uderzamy refresh tokenem do serwera autentykacyjnego, tyle że w Springu refresh tokeny są zablokowane, jeśli uwierzytelniamy się loginem i hasłem.

Ale temat jest o przełączaniu konta użytkownika, czyli jak zmusić Spring Security, żeby odczytał z JWT token istniejącego użytkownika i zwrócił token JWT dla tego, na którego chcemy się przełączyć. Jeśli nie da się tego zrobić w prosty sposób, no to w takim razie trzeba napisać własny filtr.

0

Przełączenie konta == logout + login? 😅

0

@Charles_Ray: Pod warunkiem, że zapamiętamy w aplikacji login i hasło użytkownika, co nie jest bezpieczne. Wystarczy atak XSS i wykonanie

sessionStorage.get('password')

Z drugiej strony gdzieś trzeba też trzymać token JWT i na razie ładuję go do sessionStorage. Też błąd. Podobno zaleca się ciastka z httpOnly. Ale wtedy trzeba by zmienić sposób wysyłki JWT, nie w nagłówku Authorization (tak to chyba działa w większości apek webowych), tylko w ciastku. A czy jest bezpieczniejsza alternatywa?

Jako load balancer stoi Envoy i on też umie czytać JWT z ciastek.

0

W gmailu/youtubie jak przełączasz konto też możesz zostać poproszony o uwierzytelnienie się. Ciekawe co się dzieje z ciastkiem po przełączeniu konta, może dostaje się nowe?

Sama lista kont (dane dla komponentu na UI) może spokojnie być zapisywana w local storage.

Raczej koncept powiązanych kont to jest coś, co będziesz musiał zakodzić sam.

0

Użytkownik != konto
Możesz zrobić tak, że serwis wystawiający JWT umieszcza w nim informację do jakich kont token ma zapewnić dostęp:

Logujesz się jako Adam
dostajesz JWT z jakimś tam id użytkownika i dopisanym "Adam"

Używając logujesz się jako Bartek, ale jednocześnie przesyłając JWT "Adam"
Dostajesz JWT z informacją o dostępie do kont "Adam", "Bartek"

Każde odświeżenie tokenu zwraca ci nowy JWT zawierający informację o wszystkich kontach do których jesteś zalogowany

Tak stworzonego tokenu używasz do autoryzacji dostępu do zasobu, w dodatkowym nagłówku wysyłając aktualne alter ego.

1 użytkowników online, w tym zalogowanych: 0, gości: 1