Przekazywanie JWT tokena użytkownika przez wiele mikroserwisów

0

Cześć,
Załóżmy taki przypadek - mamy wiele mikroserwisów. Klient wysyła request zwierający token JWT do jednego z nich. Ten serwis musi skomunikować się z innymi, one zaś z kolejnymi. W każdym kolejnym requestcie potrzebny jest token JWT podany przez klienta.

Każdy mikroserwis napisany jest w miarę sensownie (tzn domena serwisu oddzielona od detali implementacyjnych, frameworków itd.).

I teraz pojawia się pytanie - powiedzmy pierwszy serwis dostał request z tokenem. Jego domena mówi, że powinien wykonać akcję, która pod spodem oznacza strzał do jakiegoś innego serwisu. Ten strzał wymaga tego tokena co go dostaliśmy. W jaki sposób przekazać ten token, który przyszedł w requestcie przez domenę serwisu, żeby nie zrobić wycieku "do domeny", że używamy tokenów JWT (domeny to nie powinno interesować)? Możnaby też nie pchać tego przez domenę tylko założyć, że "ten controller co przyjął request to sobie sprytnie przekaże ten token do implementacji tego interfejsu OrderService co to tego tokena potrzebuje" - ale to tworzy dziwaczny coupling miedzy nimi.

Jakieś pomysły? Jak to robicie? Może być technicznie, może być bardziej koncepcyjnie.

4
  1. W ogóle tak nie robić? Mieć warstwę security na wejściu która ustali role i ACLe użytkownika i pomiędzy serwisami opierać się na tym że serwis zlecający wie co robi. W efekcie serwis zlecejący wysyła co najwyżej swoje własne JWT albo uwierzytelnia się jakoś inaczej w serwisie docelowym.
  2. Możesz zawsze gdzieśtam utworzyć sobie obiekt JWTAuthenticatedExternalServiceClient który wysyła requesty z odpowiednimi credentialami, a w domenie widzisz ten obiekt tylko jako jakiś interfejs ExternalServiceClient i nie wiesz wcale ani jak sie komunikuje ani co tam wysyła.
0

dzięki za odp @Shalom

  1. No tak, zrobienie tego w 1 miejscu na wejściu to jedno z rozwiązań. Też o tym myślałem. Ztcw centralna autoryzacja jak i autoryzacja per-mikroserwis to po prostu dwa podejścia w których żadne nie jest lepsze niż drugie. Wszystko zależy od przypadku. Obecnie zastosowane jest to drugie podejście i dopiero się klaruje czy to pierwsze miałoby sens. Dlatego pytam w kontekście stanu obecnego.
    Btw - w podejściu gdzie autentykacja/authoryzacja jest centralna (w warstwie na wejściu tj mówisz) mikroserwisy nie otrzymują wcale tokenów? tylko dane w endpointach/body?

  2. Tylko pytanie skąd ten JWTAuthenticatedExternalServiceClient będzie miał ten token, skoro metode na interfejsie woła domena.

0

@Shalom:

opierać się na tym że serwis zlecający wie co robi.

to w ogóle ma sens? jeżeli ktoś nam buchnie ms1, to nagle może w całym systemie robić co chce?

0

@azalut

  1. A po co miałyby dostawać tokeny w takiej sytuacji? Tzn no mogą, ale po to zeby z tokenu wyjąć sobie jakieś role i ACLe i potem zapominają skąd to przyszło i w jakiej formie. W tym przypadku token to tylko nośnik. Jakbyś dostawał Cookie do jakiegoś SSO to idea byłaby podobna, pobierasz dane usera a potem już nie przekazujesz dalej tego cookie :)
  2. No ale do domeny możesz sobie z kontrolera przekazać taki obiekt przecież. Równie dobrze mógłbyś przekazać jakiś swój AutorizationHolder i domena nie musi wcale wiedzieć co on robi albo co trzyma, tylko że ma go przekazać gdzieś dalej.

@WeiXiao i tak bedzie mógł. Przecież moze sobie równie dobrze kraść tokeny od userów skoro kontroluje jeden z twoich serwisów do których chcesz te tokeny wysyłać? :) Nie widzę specjalnie różnicy. A w sytuacji którą opisałem dany serwis najwyżej może robić to co danemu serwisowi wolno i nic więcej bo wysyła requesty w swoim imieniu a nie w imieniu usera.

0

@Shalom

No dobra, a idąc dalej, to jeżeli mamy MS1 który odbiera requesty od usera i dalej coś wysyła do MS2 (wewnętrzny MS?), to MS2 waliduje czy dostaje request od MS1?

MS2 jest w ogóle wystawiony na świat? no chyba tak?

jak on się broni, aby ktoś sobie ot tak, po prostu nie wysłał do niego bezpośrednio requesta?

0

to MS2 waliduje czy dostaje request od MS1?

Czytaj co napisałem wyżej:

W efekcie serwis zlecejący wysyła co najwyżej swoje własne JWT albo uwierzytelnia się jakoś inaczej w serwisie docelowym.

Więc tak, oczywiście że tak robi.

MS2 jest w ogóle wystawiony na świat? no chyba tak?

Niekoniecznie, ale to bez znaczenia.

0

Chyba rozumiem, o co chodzi. Można pchać takie rzeczy przez ThreadLocal, tak jak cały kontekst security w Springu. Czyli na wejściu masz filtr, a na wyjściu interceptor na kliencie http. W momencie kiedy danej usługi w łańcuszku wywołań nie interesują role itd słabo było zanieczyszczać cały kod przekazywaniem tokena explicite. Jak to testować? Integracyjnie.

Oczywiście w momencie przejścia na reactive, to przestaje działać, ale da się łatwo poprawić.

Można jeszcze autoryzować same usługi używając client_credentials (szczególnie przydatne przy komunikacji asynchronicznej, kiedy nie ma tokenu) lub korzystając z service mesh.

1

Żadnych ThreadLocal i innych zmiennych globalnych ( po prawdzie nawet gorszych niż globalne). To dla lamoszczaków, robi problemy przy testach i wielowątkowości - jakieś korutyny i masz śmierć w burakach.

Weź ReaderMonad jak człowiek.

Po to zostało stworzone. Niech twoje serwisy zamiast "robić calla" zwracają funkcje, która zrobi calla jak dostanie token czy jakiś tam kontekst. Taka funkcja (Token)->A to w zasadzie ReaderMonad. Żeby była monada to formalnie musiałbyś napisać flatMap - możliwe, że nie potrzebujesz. (flatMap przyda Ci się jak masz kolejne zależne od siebie wywołania serwisów).

Inaczej możesz sobie to zapisać jako Function<Token, A> (gdzie A jest generyczne - rezultat).

Oczywiście, można powiedzieć, że niewiele się to różni od trzymania parametru token we wszystkich argumentach - i przemycenia Tokenu do domeny. To prawda,
jakkolwiek, korzystając z readerMonad zobaczysz, że generalnie nie musisz przynajmniej wszędzie tego tokena przepisywać - masz go tylko (i aż) w API po stronie rezultatu.
Następnym krokiem jest ukrycie Tokena pod specjalizowaną monadą, która opakowuje (enkapsuluje) całą infrastruktura (bo tak naprawdę, w praktyce zwykle przydaje się dużo więcej niż tylko security context).

W swoim frameworku https://github.com/neeffect/nee mam taką monadę Nee, która trzyma wszystkie takie kontekstowe rzeczy. To taki odpowiednik IO<A> czy Future<A>, a tak naprawdę najbliżej temu do ZIO<R,A,E> - zresztą wzorowałem się.

0

To podejście gdzie serwis ma własne JWT/dowolne ID ma sens wtedy i tylko wtedy gdy masz warstwę "niepubliczną". Część dużych banków działa tak, że:

  • mamy wystawioną usługę, która jest dostępna publicznie
  • mamy jakąś tam liczbę usług, które siedzą za jakąś dodatkową zaporą.

Np.
Usługa A: /public/user zwraca dane o użytkownikach za np. ostatni tydzień. Taki raport jest ograniczony, np. do jednego miesiąca. Usługa ta korzysta z drugiej usługi:
Usługa B: /internal/user - do niej można się dostać tylko po uwierzytelnieniu się unikalnym certyfikatem. W rezultacie masz gwarancję (z dokładnością do tego jak bardzo upilnujesz certyfikatu), że usługa B będzie niedostępna z zewnątrz, a użytkownik z dostępem do usługi A co najwyżej dostanie odpowiednio przefiltrowane dane.

Wydaje mi się, że jeśli zamiast certyfikatu wstawisz drugą usługę OAuthową to będzie działać podobnie. Natomiast jeśli usługa A i B są dostępne publicznie to nie ma oczywiście żadnego sensu.

0

A nie prościej mieć osobna autentykacje pomiędzy serwerami? Lub po prostu VPN?
User uderza do serwisu z JWT, a dalej to już nas JWT usera nie interesuje.

0

I teraz pojawia się pytanie - powiedzmy pierwszy serwis dostał request z tokenem. Jego domena mówi, że powinien wykonać akcję, która pod spodem oznacza strzał do jakiegoś innego serwisu. Ten strzał wymaga tego tokena co go dostaliśmy. W jaki sposób przekazać ten token, który przyszedł w requestcie przez domenę serwisu, żeby nie zrobić wycieku "do domeny", że używamy tokenów JWT (domeny to nie powinno interesować)?

Zakładając że tak jak piszesz każdy mikroserwis jest napisany "sensownie" to można to zrobić bardzo łatwo. Warstwa domeny działa na abstrakcjach, jedną z tych abstrakcji będzie zewnętrzny serwis z którym logika biznesowa musi się skomunikować. Implementacja tego serwisu (warstwa infrastruktury) korzystająca z jakiegoś klienta HTTP będzie wiedzieć że w requestach musi wstrzyknąć token JWT (np. jako header) i będzie to robić. Skąd weźmie token? Do tego również można użyć abstrakcji, gdzie coś- zależnie od technologii- będzie przechwytywało token z requestów przychodzących i wstrzykiwało do aktualnego kontekstu.

Dla przykładu w ASP Core do tego posłuży middleware, i serwisy DI rejestrowane jako scoped lifetime.

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