Znalezienie wartościowego i pragmatycznego artykułu o zabezpieczaniu systemów rozproszonych nie jest proste. Ciężko powiedzieć, czy wynika to z nieśmiertelności podejścia “security by obscurity”, czy też raczej z niepewności, czy opisywane podejście jest w ogóle właściwe. W związku z tym, znacznie więcej osób woli pisać o tym, czym jest token JWT i z jakich trzech sekcji się składa.

W tym artykule zamiast podstaw bibliotek i frameworków, omówimy dostępne koncepcje zapewnienia bezpieczeństwa i poznamy technologie umożliwiające ich wdrożenie.

Kim jestem? Co mogę?

Na wstępie musimy doprecyzować dwa często błędnie rozumiane zamiennie pojęcia: autoryzacja i uwierzytelnianie (pamiętajcie: uwierzytelnianie, a nie „autentykacja” – to drugie to tylko dzika kalka z angielskiego). Autoryzacja jest związana z kontrolą dostępu. Jej rolą jest poświadczenie uprawnień użytkownika do wykonania określonych czynności. Proces uwierzytelniania ma natomiast za zadanie potwierdzić tożsamość użytkownika. Czyli:

  • uwierzytelnianie – odpowiada na pytanie: kim jestem?
  • autoryzacja – odpowiada na pytanie: co mogę zrobić?

W praktyce wykorzystywać będziemy oba procesy jednocześnie.

Koncepcja architektoniczna

W większości znanych mi rozwiązań (a już szczególnie w popularnej ostatnio architekturze mikroserwisowej) aplikacje nie są dostępne dla użytkownika bezpośrednio. W celu enkapsulacji wewnętrznej struktury i ustabilizowania topologii, komunikacja odbywa się za pośrednictwem pojedynczego komponentu wejściowego, nazywanego „API Gateway” bądź też „Edge Service”. Dodatkowo wydzielane są serwisy odpowiedzialne za zarządzanie użytkownikami i logowanie (jako jedna bądź też dwie osobne aplikacje).

Stanowość czy bezstanowość

Na szeroką adopcję standardu JWT z pewnością wpłynęło dążenie do zachowania jak największej bezstanowości. W wielu systemach nadal bardzo często wykorzystywany jest tzw. session token. Zazwyczaj jest to po prostu losowy ciąg znaków, wykorzystywany w systemie jedynie jako referencja. Aby uzyskać informację o użytkowniku czy jego uprawnieniach, należy skomunikować się z serwerem autoryzacji i wymienić token na potrzebne dane. JWT to podejście odwraca, dzięki zakodowaniu całej informacji w samym tokenie. Pociąga to za sobą oczywiście zwiększenie jego rozmiaru, jest to jednak stosunkowo niewielka cena za rozprężenie wprowadzane do systemu.

OAuth 2.0

OAuth (mówimy w tym artykule tylko o 2.0). Zapewne każdy o nim słyszał. Niektórzy nawet zaimplementowali stronę kliencką. Ale jedynie nieliczni w pełni rozumieją ten standard. Zanim zajmiemy się ogólnym omówieniem procesu, należy zaznaczyć, że OAuth jest odpowiedzialny jedynie za delegację autoryzacji i w żadnym razie nie zapewnia uwierzytelniania użytkownika!

Główne role w tym standardzie grają:

  1. Właściciel zasobu (resource owner) – encja, do której należy żądany zasób. Często jest to po prostu użytkownik systemu.
  2. Serwer zasobu (resource server) – aplikacja, która przechowuje żądany zasób.
  3. Klient (client) – aplikacja, która próbuje uzyskać dostęp do zasobu.
  4. Serwer autoryzacji (authorization server) – aplikacja, która w imieniu właściciela udziela dostępu do zasobu klientowi.

Sam OAuth nie narzuca jednego słusznego przepływu komunikacji pomiędzy powyższymi elementami. Co więcej, definiuje kilka, z których wybieramy ten właściwy dla nas. Natomiast abstrakcja głównej idei wygląda następująco:

  1. Klient potrzebuje w imieniu właściciela uzyskać dostęp do zasobu.
  2. W tym celu przekierowuje użytkownika do serwera autoryzacji, który poświadcza (np. udostępniając klientowi token dostępowy) uprawnienie do zasobu.
  3. Klient, wykonując żądanie dostępu do zasobu, do serwera zasobu dołącza poświadczenie uzyskane w punkcie 2.
  4. Serwer zasobu weryfikuje poprawność poświadczenia i udostępnia zasób.

To, który możliwy przepływ wybierzemy, jest w dużej mierze determinowane przez przyjętą przez nas architekturę delegacji.

Co się dzieje na granicy?

W systemach rozproszonych wyróżnić możemy dwa główne nurty delegacji.

Słaba delegacja (Poor Man’s Delegation). W tym wypadku użytkownik uzyskuje token bezpośrednio z serwera autoryzacji i dołącza go do każdego żądania, które API Gateway po prostu przekazuje dalej bez jakiejkolwiek ingerencji. Rozwiązanie to jest całkowicie bezstanowe, a komunikacja z serwerem autoryzacji ograniczona jest jedynie do nadawania tokenów. Minusem jest natomiast poważny problem z wylogowaniem użytkownika. Ponieważ token nadawany jest na określony czas, po jego kradzieży atakujący będzie mógł bez przeszkód działać w systemie tak długo, jak długo zachowana jest ważność tokenu. Kolejnym minusem tego podejścia jest pełna przejrzystość zawartości tokena dla użytkownika (token JWT można zdekodować zawsze, o ile nie jest on zaszyfrowany). Po zdekodowaniu możemy zobaczyć, jakie uprawnienia w systemie mamy dostępne. Co więcej, jeżeli występują błędy bezpieczeństwa w wykorzystywanych bibliotekach bądź też biblioteki te zostały nieprawidłowo zaimplementowane, zawartość tokenu można nie tylko odczytać, ale także modyfikować. Ponadto podejście to (z uwagi na potencjalnie duży rozmiar samego tokenu) może niekorzystnie wpływać na wydajność systemu.

Wymiana tokenów (token exchange). Tutaj sytuacja wygląda inaczej. Użytkownik, podobnie jak w pierwszym podejściu, otrzymuje od serwera autoryzacji token dostępowy. Nie jest on jednak bezpośrednio przekazywany w głąb systemu, ale wcześniej, na poziomie API gateway wymieniany na token wewnętrzny. Token publiczny może być albo bardzo prostym tokenem JWT, albo też losowym ciągiem znaków (obscure, jak na przykład identyfikator sesji). W wariancie z dwoma tokenami minusem jest związanie API Gateway bezpośrednio z serwerem autoryzacji i, co za tym idzie, nieco bardziej złożona architektura. Plusów mamy za to zdecydowanie więcej. Po pierwsze, znacząco uprościmy problem wylogowania użytkownika. Do API Gateway udostępniane są dwa tokeny: krótko żyjący token dostępowy (Access Token) i długo żyjący token odświeżający (Refresh Token). Ten drugi pozwala na ponowne pozyskanie tokenu dostępowego, bez ingerencji użytkownika (takiej, jak choćby kolejne logowanie) jednak po komunikacji z serwerem autoryzacji. Serwer ten przy okazji może sprawdzić, czy token nie został przypadkiem unieważniony (revoke). Ponadto, publiczny token jest znacznie mniejszy od wewnętrznego i nie niesie ze sobą żadnej użytecznej dla użytkownika informacji.

Uwierzytelnianie

Skoro OAuth nie jest narzędziem uwierzytelniania, to jak możemy sobie z tym zagadnieniem poradzić? Z pomocą przychodzi nam OpenID Connect. Jest to prosta warstwa identyfikacji (swoista nakładka) współdziałająca z OAuth. W dużym uproszczeniu polega ona na utworzeniu obok tokenu dostępowego (Access Token) także drugiego – identyfikacyjnego (ID Token). Zawiera on pełną (zapisaną w formie JWT) informację o użytkowniku, taką jak adres e-mail, imię, nazwisko, strefa czasowa etc.

Mamy token – i co dalej?

Jeżeli już omówiliśmy sposób pozyskiwania i przekazywania tokenów w głąb systemu, zastanówmy się, co dzieje się dalej. Serwis wewnętrzny, po otrzymaniu pełnego kontekstu wywołania (Access i ID Token), weryfikuje jego poprawność i dekoduje informacje. Dzięki temu wiemy, w imieniu jakiego użytkownika wykonywane jest żądanie, a także jakie posiada on uprawnienia. Dalszy przebieg procesu nie różni się niczym od obsługi użytkownika zalogowanego za pomocą klasycznej sesji.

Tak w zarysie wygląda koncepcja zapewnienia bezpieczeństwa w systemach rozproszonych. Należy jednak zaznaczyć, że same systemy autoryzacji i uwierzyteleniania, nawet poprawnie zaimplementowane nie gwarantują pełnego bezpieczeństwa. Użytkownik zapisujący hasło na kartce przyklejonej do monitora skutecznie niweczy pracę architektów. To jednak temat na zupełnie inny wpis.

W tym miejscu chciałbym też podziękować Błażejowi Bucko za jego recenzję i wkład merytoryczny w artykuł.

A Ty stosujesz jakąś ciekawą architekturę zabezpieczeń w swoim systemie? Zachęcam do podzielenia się doświadczeniami w komentarzach.

1
Dodaj komentarz

avatar
1 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
Robert Recent comment authors
  Subscribe  
najnowszy najstarszy oceniany
Powiadom o
Robert
Gość

Krótko, zwięźle i na temat. Tak jak wspomniałeś, to tylko zarys i namiastka wiedzy wokół szeroko pojętego uwierzytelniania i autoryzacji.
Robiłem kiedyś prezentacje na ten temat, gdzie oprócz pokazania jak działa OAuth, mówiłem też o innych standardach/protokołach bezpieczeństwa: https://speakerdeck.com/witek1902/proces-uwierzytelniania-uzytkownika

Warto zerknąć też na prezentacje z Confitury „2015 – Bolesław Dawidowicz – Prezentacja o standardach i protokołach bezpieczeństwa”: https://www.youtube.com/watch?v=69B5EHznGu8