Zabezpiecz aplikacje w chmurze za pomocą OAuth 2.0 i OpenID Connect
Kompletny przewodnik po zabezpieczaniu aplikacji w chmurze za pomocą OAuth 2.0 i OpenID Connect oraz jak zapewnić doskonałe doświadczenie użytkownika dzięki uwierzytelnianiu i autoryzacji.
Wprowadzenie
Aplikacje chmurowe to obecnie trend. Choć typ aplikacji może się różnić (web, mobilne, desktopowe itp.), wszystkie mają zaplecze chmurowe zapewniające usługi takie jak przechowywanie, obliczenia i bazy danych. Zazwyczaj te aplikacje muszą uwierzytelnić użytkowników i nadać im uprawnienia do dostępu do określonych zasobów.
Choć możliwe są własnoręcznie skonstruowane mechanizmy uwierzytelniania i autoryzacji, bezpieczeństwo stało się jednym z głównych priorytetów podczas tworzenia aplikacji chmurowych. Na szczęście nasza branża ma sprawdzone standardy, takie jak OAuth 2.0 i OpenID Connect, które pomagają nam w implementacji bezpiecznego uwierzytelniania i autoryzacji.
Ten post zakłada następujące:
- Masz podstawową wiedzę na temat tworzenia aplikacji (web, mobilnych lub innego typu).
- Słyszałeś o koncepcjach uwierzytelniania i autoryzacji.
- Słyszałeś o OAuth 2.0 i OpenID Connect.
Tak, "słyszałeś" wystarcza dla punktów 2 i 3. Ten post używa przykładów z rzeczywistego świata do wyjaśnienia koncepcji i ilustrowania procesu za pomocą diagramów. Zaczynajmy!
OAuth 2.0 vs. OpenID Connect
Jeśli znasz już OAuth 2.0 i OpenID Connect, możesz kontynuować czytanie, ponieważ omówimy tu pewne rzeczywiste przykłady. Jeśli te standardy są dla ciebie nowe, również jest bezpiecznie kontynuować, gdyż wprowadzimy je w prosty sposób.
OAuth 2.0
OAuth 2.0 to framework autoryzacji, który pozwala aplikacji uzyskać ograniczony dostęp do chronionych zasobów w innej aplikacji w imieniu użytkownika lub samej aplikacji. Większość popularnych usług, takich jak Google, Facebook i GitHub, używa OAuth 2.0 do logowania społecznościowego (np. "Zaloguj się za pomocą Google").
Na przykład masz aplikację webową MyApp, która chce uzyskać dostęp do Google Drive użytkownika. Zamiast prosić użytkownika o udostępnienie swoich danych uwierzytelniających Google Drive, MyApp może użyć OAuth 2.0 do zażądania dostępu do Google Drive w imieniu użytkownika. Oto uproszczony przepływ:
W tym przepływie, MyApp nigdy nie widzi danych uwierzytelniających Google Drive użytkownika. Zamiast tego otrzymuje token dostępu od Google, który pozwala mu na dostęp do Google Drive w imieniu użytkownika.
W terminologii OAuth 2.0, MyApp jest klientem, Google jest zarówno serwerem autoryzacji, jak i serwerem zasobów dla prostoty. W rzeczywistości często mamy oddzielne serwery autoryzacji i zasobów, aby oferować doświadczenie pojedynczego logowania (SSO). Na przykład, Google jest serwerem autoryzacji i może mieć wiele serwerów zasobów, takich jak Google Drive, Gmail i YouTube.
Należy zauważyć, że rzeczywisty przepływ autoryzacji jest bardziej złożony niż ten. OAuth 2.0 ma różne typy grantów, zakresy i inne koncepcje, o których powinieneś wiedzieć. Zostawmy to na chwilę i przejdźmy do OpenID Connect.
OpenID Connect (OIDC)
OAuth 2.0 jest świetny do autoryzacji, ale możesz zauważyć, że nie ma sposobu na identyfikację użytkownika (czyli uwierzytelnianie). OpenID Connect to warstwa tożsamości na szczycie OAuth 2.0, która dodaje możliwości uwierzytelniania.
W powyższym przykładzie, MyApp musi wiedzieć, kim jest użytkownik, zanim rozpocznie przepływ autoryzacji. Zauważ, że tutaj zaangażowani są dwaj użytkownicy: użytkownik MyApp i użytkownik Google Drive. W tym przypadku, MyApp musi poznać użytkownika własnej aplikacji.
Spójrzmy na prosty przykład, zakładając, że użytkownicy mogą zalogować się do MyApp używając nazwy użytkownika i hasła:
Ponieważ uwierzytelniamy użytkownika naszej własnej aplikacji, zazwyczaj nie ma potrzeby prosić o zgodę tak, jak to zrobił Google w przepływie OAuth 2.0. Tymczasem potrzebujemy czegoś, co potrafi zidentyfikować użytkownika. OpenID Connect wprowadza koncepcje, takie jak token ID i punkt końcowy userinfo, które nam w tym pomogą.
Możesz zauważyć, że dostawca tożsamości (IdP) jest nowym, samodzielnym uczestnikiem w przepływie. Jest to samo, co serwer autoryzacji w OAuth 2.0, ale dla lepszej klarowności, używamy terminu IdP, aby pokazać, że jest odpowiedzialny za uwierzytelnianie użytkowników i zarządzanie tożsamościami.
Kiedy twój biznes rośnie, możesz mieć wiele aplikacji, które dzielą tę samą bazę użytkowników. Podobnie jak OAuth 2.0, OpenID Connect pozwala mieć jeden serwer autoryzacji, który potrafi uwierzytelnić użytkowników dla wielu aplikacji. Jeśli użytkownik jest już zalogowany w jednej aplikacji, nie musi ponownie wpisywać swoich danych uwierzytelniających, gdy inna aplikacja przekierowuje go do IdP. Przepływ może być przeprowadzony automatycznie bez interakcji użytkownika. To się nazywa pojedyncze logowanie (SSO).
Ponownie, jest to mocno uproszczony przepływ i pod maską jest jeszcze więcej szczegółów. Na razie przejdźmy do następnej sekcji, aby zapobiec przeciążeniu informacyjnemu.
Typy aplikacji
W poprzedniej sekcji użyliśmy aplikacji webowych jako przykładów, podczas gdy świat jest bardziej różnorodny. Dla dostawcy tożsamości, dokładny język programowania, framework czy platforma, której używasz, nie mają tak naprawdę znaczenia. W praktyce jedną istotną różnicą jest to, czy aplikacja jest klientem publicznym czy klientem prywatnym (zaufanym):
- Klient publiczny: klient, który nie może utrzymać swoich danych uwierzytelniających w tajemnicy, co oznacza, że właściciel zasobu (użytkownik) ma do nich dostęp. Na przykład aplikacja webowa uruchamiana w przeglądarce (np. aplikacja jednostronicowa).
- Klient prywatny: klient, który jest w stanie utrzymać swoje dane uwierzytelniające w tajemnicy bez ich ujawniania użytkownikom (właścicielom zasobu). Na przykład aplikacja webowa uruchamiana na serwerze (np. aplikacja webowa po stronie serwera) lub usługa API.
Mając to na uwadze, zobaczmy, jak OAuth 2.0 i OpenID Connect mogą być używane w różnych typach aplikacji.
"Aplikacja" i "klient" mogą być używane zamiennie w kontekście tego posta.
Aplikacje webowe uruchomione na serwerze
Aplikacja działa na serwerze i dostarcza strony HTML użytkownikom. Wiele popularnych frameworków webowych, takich jak Express.js, Django i Ruby on Rails, zalicza się do tej kategorii; a frameworki typu backend-for-frontend (BFF), jak Next.js i Nuxt.js, również są uwzględnione. Te aplikacje mają następujące cechy:
- Ponieważ serwer pozwala tylko na prywatny dostęp (użytkownicy publiczni nie mają dostępu do kodu ani danych uwierzytelniających serwera), uważa się go za klienta prywatnego.
- Ogólny przepływ uwierzytelniania użytkownika jest taki sam jak ten, którego omówiliśmy w sekcji "OpenID Connect".
- Aplikacja może używać tokenu ID wydanego przez dostawcę tożsamości (tj. dostawcę OpenID Connect) do identyfikacji użytkownika i wyświetlania treści specyficznych dla użytkownika.
- Aby utrzymać aplikację w bezpieczeństwie, aplikacja zazwyczaj używa przepływu autoryzacji kodu do uwierzytelniania użytkownika i uzyskiwania tokenów.
Tymczasem aplikacja może potrzebować dostępu do innych wewnętrznych usług API w architekturze mikroserwisów; lub jest to aplikacja monolityczna, która potrzebuje kontrolę dostępu dla różnych części aplikacji. Omówimy to w sekcji "Chroń swoje API".
Aplikacje jednostronicowe (SPA)
Aplikacja działa w przeglądarce użytkownika i komunikuje się z serwerem za pomocą API. React, Angular i Vue.js to popularne frameworki do tworzenia SPA. Te aplikacje mają następujące cechy:
- Ponieważ kod aplikacji jest widoczny dla publiczności, uważa się go za klienta publicznego.
- Ogólny przepływ uwierzytelniania użytkownika jest taki sam jak ten, którego omówiliśmy w sekcji "OpenID Connect".
- Aplikacja może używać tokenu ID wydanego przez dostawcę tożsamości (tj. dostawcę OpenID Connect) do identyfikacji użytkownika i wyświetlania treści specyficznych dla użytkownika.
- Aby utrzymać aplikację w bezpieczeństwie, aplikacja zazwyczaj używa przepływu autoryzacji kodu z PKCE (Proof Key for Code Exchange) do uwierzytelniania użytkownika i uzyskiwania tokenów.
Zazwyczaj SPA potrzebują dostępu do innych usług API do pobierania i aktualizowania danych. Omówimy to w sekcji "Chroń swoje API".
Aplikacje mobilne
Aplikacja działa na urządzeniu mobilnym (iOS, Android itp.) i komunikuje się z serwerem za pomocą API. W większości przypadków te aplikacje mają te same cechy co SPA.
Aplikacje maszyna-do-maszyny (M2M)
Aplikacje maszyna-do-maszyny to klienci, które działają na serwerze (maszyna) i komunikują się z innymi serwerami. Te aplikacje mają następujące cechy:
- Podobnie jak aplikacje webowe uruchomione na serwerze, aplikacje M2M są klientami prywatnymi.
- Aplikacja może nie potrzebować identyfikacji użytkownika; zamiast tego musi uwierzytelnić się, aby uzyskać dostęp do innych usług.
- Aplikacja może używać tokenu dostępu wydanego przez dostawcę tożsamości (tj. dostawcę OAuth 2.0) do uzyskania dostępu do innych usług.
- Aby utrzymać aplikację w bezpieczeństwie, aplikacja zazwyczaj używa przepływu danych uwierzytelniających klienta do uzyskiwania tokenów dostępu.
Podczas uzyskiwania dostępu do innych usług, aplikacja może potrzebować dostarczyć token dostępu w nagłówku żądania. Omówimy to w sekcji "Chroń swoje API".
Aplikacje działające na urządzeniach IoT
Aplikacja działa na urządzeniu IoT (np. urządzeniach inteligentnego domu, nosidłach itp.) i komunikuje się z serwerem za pomocą API. Te aplikacje mają następujące cechy:
- W zależności od możliwości urządzenia może to być klient publiczny lub prywatny.
- Ogólny przepływ uwierzytelniania może różnić się od tego, o którym mówiliśmy w sekcji "OpenID Connect" w zależności od możliwości urządzenia. Na przykład niektóre urządzenia mogą nie posiadać ekranu umożliwiającego użytkownikom wpisanie swoich danych uwierzytelniających.
- Jeśli urządzenie nie potrzebuje identyfikacji użytkownika, może nie potrzebować używać tokenów ID ani punktów końcowych userinfo; zamiast tego może być traktowane jako aplikacja maszyna-do-maszyny (M2M).
- Aby utrzymać aplikację w bezpieczeństwie, aplikacja może używać przepływu autoryzacji kodu z PKCE (Proof Key for Code Exchange) do uwierzytelniania użytkownika i uzyskiwania tokenów lub przepływu danych uwierzytelniających klienta do uzyskiwania tokenów dostępu.
Podczas komunikacji z serwerem urządzenie może potrzebować dostarczyć token dostępu w nagłówku żądania. Omówimy to w sekcji "Chroń swoje API".
Chroń swoje API
Dzięki OpenID Connect, możliwe jest identyfikowanie użytkownika i pobieranie danych specyficznych dla użytkownika za pomocą tokenów ID lub punktów końcowych userinfo. Proces ten nazywa się uwierzytelnianiem. Jednak prawdopodobnie nie chcesz, aby wszystkie twoje zasoby były dostępne dla wszystkich uwierzytelnionych użytkowników, na przykład tylko administratorzy mogą mieć dostęp do strony zarządzania użytkownikami.
Tutaj wchodzi w grę autoryzacja. Pamiętaj, że OAuth 2.0 to framework autoryzacji, a OpenID Connect to warstwa tożsamości na szczycie OAuth 2.0; co oznacza, że możesz również używać OAuth 2.0, gdy OpenID Connect jest już w miejscu.
Przypomnijmy sobie przykład, którego użyliśmy w sekcji "OAuth 2.0": MyApp chce uzyskać dostęp do Google Drive użytkownika. Nie jest praktyczne pozwolić MyApp uzyskać dostęp do wszystkich plików użytkownika w Google Drive. Zamiast tego MyApp powinien jawnie zgłosić, co chce uzyskać (np. dostęp tylko do odczytu do plików w określonym folderze). W terminologii OAuth 2.0 nazywa się to zakresem.
Możesz zobaczyć termin "uprawnienie" używany zamiennie z "zakresem" w kontekście OAuth 2.0, ponieważ czasami "zakres" jest niejednoznaczny dla użytkowników nietechnicznych.
Kiedy użytkownik przyznaje dostęp do MyApp, serwer autoryzacji wydaje token dostępu z zażądanym zakresem. Token dostępu jest następnie wysyłany do serwera zasobów (Google Drive), aby uzyskać dostęp do plików użytkownika.
Naturalnie, możemy zastąpić Google Drive naszymi własnymi usługami API. Na przykład MyApp musi uzyskać dostęp do OrderService, aby pobrać historię zamówień użytkownika. Tym razem, ponieważ uwierzytelnianie użytkownika jest już przeprowadzone przez dostawcę tożsamości i zarówno MyApp, jak i OrderService są pod naszą kontrolą, możemy pominąć prośbę do użytkownika o przyznanie dostępu; MyApp może bezpośrednio wysłać żądanie do OrderService z tokenem dostępu wydanym przez dostawcę tożsamości.
Token dostępu może zawierać read:order
zakres, aby wskazać, że użytkownik może odczytać swoją historię zamówień.
Załóżmy teraz, że użytkownik przypadkowo wpisuje URL strony administratora w przeglądarce. Ponieważ użytkownik nie jest administratorem, w tokenie dostępu nie ma admin
zakresu. OrderService odrzuci żądanie i zwróci komunikat błędu.
W tym przypadku OrderService może zwrócić kod statusu 403 Forbidden, aby wskazać, że użytkownik nie jest uprawniony do uzyskania dostępu do strony administratora.
Dla aplikacji maszyna-do-maszyny (M2M), nie uczestniczy w tym procesie żaden użytkownik. Aplikacje mogą bezpośrednio zażądać tokenów dostępu od dostawcy tożsamości i użyć ich do uzyskania dostępu do innych usług. Ten sam koncept nadal ma zastosowanie: token dostępu zawiera odpowiednie zakresy dla uzyskania dostępu do zasobów.
Projektowanie autoryzacji
Widać dwie ważne rzeczy do rozważenia przy projektowaniu autoryzacji do ochrony twoich usług API:
- Zakresy: Definiowanie, do czego klient ma dostęp. Zakresy mogą być drobnoziarniste (np.
read:order
,write:order
) lub bardziej ogólne (np.order
) w zależności od twoich wymagań. - Kontrola dostępu: Definiowanie, kto może mieć poszczególne zakresy. Na przykład, tylko administratorzy mogą mieć
admin
zakres.
Jeśli chodzi o kontrolę dostępu, popularne podejścia to:
- Kontrola dostępu oparta na rolach (RBAC): Przydzielanie ról użytkownikom i definiowanie, jakie role mają dostęp do jakich zasobów. Na przykład, rola administratora może mieć dostęp do strony administratora.
- Kontrola dostępu oparta na atrybutach (ABAC): Definiowanie zasad opartych na atrybutach (np. dział użytkownika, lokalizacja itp.) i podejmowanie decyzji o kontroli dostępu w oparciu o te atrybuty. Na przykład, użytkownik z działu "Inżynieria" może mieć dostęp do strony inżynieryjnej.
Warto wspomnieć, że dla obu podejść standardowym sposobem weryfikacji kontroli dostępu jest sprawdzanie zakresów tokenu dostępu, zamiast ról czy atrybutów. Role i atrybuty mogą być bardzo dynamiczne, a zakresy są bardziej statyczne, co znacznie ułatwia zarządzanie.
Dla szczegółowych informacji na temat kontroli dostępu, możesz odnieść się do RBAC i ABAC: Modele kontroli dostępu, które powinieneś znać.
Tokeny dostępu
Chociaż wiele razy wspominaliśmy o terminie "token dostępu", nie omówiliśmy jak go uzyskać. W OAuth 2.0 token dostępu jest wydawany przez serwer autoryzacji (dostawcę tożsamości) po pomyślnym zakończeniu przepływu autoryzacji.
Przyjrzyjmy się bliżej przykładzie z Google Drive i załóżmy, że używamy przepływu kodu autoryzacji:
Istnieją ważne kroki w przepływie dotyczącym wydawania tokenu dostępu:
- Krok 2 (Przekieruj do Google): MyApp przekierowuje użytkownika do Google z żądaniem autoryzacji. Zazwyczaj to żądanie zawiera następujące informacje:
- Który klient (MyApp) inicjuje żądanie
- Jakie zakresy MyApp żąda
- Gdzie Google powinien przekierować użytkownika po zakończeniu autoryzacji
- Krok 5 (Poproś o zgodę na dostęp do Google Drive): Google prosi użytkownika o przyznanie dostępu do MyApp. Użytkownik może zdecydować się na przyznanie lub odmowę dostępu. Ten krok nazywa się zgodą.
- Krok 7 (Przekieruj do MyApp z danymi autoryzacji): Ten krok został nowo wprowadzony w diagramie. Zamiast bezpośrednio zwracać token dostępu, Google zwraca jednorazowy kod autoryzacji do MyApp dla bardziej bezpiecznej wymiany. Kod ten jest używany do uzyskania tokenu dostępu.
- Krok 8 (Użyj kodu autoryzacji do wymiany na token dostępu): To również nowy krok. MyApp wysyła kod autoryzacji do Google, aby wymienić go na token dostępu. Jako dostawca tożsamości, Google złoży kontekst żądania i zdecyduje, czy wydać token dostępu:
- Klient (MyApp) jest tym, za kogo się podaje
- Użytkownik przyznał dostęp klientowi
- Użytkownik jest tym, za kogo się podaje
- Użytkownik ma niezbędne zakresy
- Kod autoryzacji jest ważny i nie wygasł
Powyższy przykład zakłada, że serwer autoryzacji (dostawca tożsamości) i serwer zasobów to to samo (Google). Jeśli są one rozdzielone, biorąc przykład MyApp i OrderService, przepływ będzie wyglądał tak:
W tym przepływie serwer autoryzacji (IdP) wydaje zarówno token ID, jak i token dostępu do MyApp (krok 8). Token ID jest używany do identyfikacji użytkownika, a token dostępu jest używany do uzyskania dostępu do innych usług, takich jak OrderService. Ponieważ zarówno MyApp, jak i OrderService są usługami pierwszej strony, zazwyczaj nie proszą użytkownika o przyznanie dostępu; zamiast tego polegają na kontroli dostępu w dostawcy tożsamości, aby stwierdzić, czy użytkownik może uzyskać dostęp do zasobów (tj. czy token dostępu zawiera niezbędne zakresy).
Na koniec przyjrzyjmy się, jak token dostępu jest używany w aplikacjach maszyna-do-maszyny (M2M). Ponieważ w procesie nie uczestniczy żaden użytkownik, a aplikacja jest zaufana, może bezpośrednio zażądać tokenu dostępu od dostawcy tożsamości:
Kontrola dostępu może nadal być tutaj stosowana. Dla OrderService nie ma znaczenia, kim jest użytkownik ani która aplikacja żąda danych; ważne jest tylko token dostępu i zakresy, które zawiera.
Tokeny dostępu są zazwyczaj kodowane jako JSON Web Tokens (JWT). Aby dowiedzieć się więcej o JWT, możesz odnieść się do Co to jest JSON Web Token (JWT)?.
Wskaźniki zasobów
OAuth 2.0 wprowadza koncepcję zakresów dla kontroli dostępu. Jednak możesz szybko zauważyć, że zakresy nie są wystarczające:
- OpenID Connect definiuje zestaw standardowych zakresów, takich jak
openid
,offline_access
iprofile
. Może być mylące mieszanie tych standardowych zakresów z własnymi zakresami. - Możesz mieć wiele usług API, które dzielą tę samą nazwę zakresu, ale mają różne znaczenia.
Jednym z powszechnych rozwiązań jest dodawanie sufiksów (lub prefiksów) do nazw zakresu, aby wskazać zasób (usługę API). Na przykład, read:order
i read:product
są bardziej jasne niż read
i read
. Rozszerzenie OAuth 2.0 RFC 8707 wprowadza nowy parametr resource
, aby wskazać serwer zasobów, do którego klient chce uzyskać dostęp.
W rzeczywistości, usługi API są zazwyczaj definiowane za pomocą URL, więc bardziej naturalne jest używanie URL jako wskaźników zasobów. Na przykład, API OrderService może być reprezentowane jako:
Jak widzisz, parametr ten przynosi wygodę używania rzeczywistych URL zasobów w żądaniach autoryzacyjnych i tokenach dostępu. Warto wspomnieć, że RFC 8707 może nie być zaimplementowane przez wszystkich dostawców tożsamości. Powinieneś sprawdzić dokumentację swojego dostawcy tożsamości, aby zobaczyć, czy obsługuje parametr resource
(Logto go obsługuje).
W skrócie, parametr resource
może być używany w żądaniach autoryzacyjnych i tokenach dostępu, aby wskazać zasób, do którego klient chce uzyskać dostęp.
Nie ma żadnych ograniczeń dotyczących dostępności wskaźników zasobów, tj. wskaźnik zasobów nie musi być rzeczywistym URL, który wskazuje na usługę API. Dlatego nazwa "wskaźnik zasobów" odpowiednio odzwierciedla jego rolę w procesie autoryzacji. Możesz używać wirtualnych URL do reprezentowania zasobów, które chcesz chronić. Na przykład, możesz zdefiniować wirtualny URL
https://api.example.com/admin
w swojej aplikacji monolitycznej, aby reprezentować zasób, do którego dostęp mogą mieć tylko administratorzy.
Połącz to wszystko
W tym momencie omówiliśmy podstawy OAuth 2.0 i OpenID Connect, oraz jak ich używać w różnych typach aplikacji i scenariuszach. Choć omówiliśmy je oddzielnie, możesz je łączyć zgodnie z wymaganiami biznesowymi. Ogólna architektura może być tak prosta jak ta:
Albo nieco bardziej skomplikowana:
Gdy twoja aplikacja rośnie, zauważysz, że dostawca tożsamości (IdP) odgrywa kluczową rolę w architekturze; ale nie wiąże się bezpośrednio z celami biznesowymi. Choć świetnym pomysłem jest przesiadanie go na zaufanego dostawcę, musimy mądrze wybierać dostawcę tożsamości. Dobry dostawca tożsamości może znacznie upraszczać proces, redukować wysiłek programistyczny i chronić przed potencjalnymi pułapkami związanymi z bezpieczeństwem.
Zakończenie
Dla nowoczesnych aplikacji chmurowych, dostawca tożsamości (lub serwer autoryzacji) jest centralnym miejscem dla uwierzytelniania użytkowników, zarządzania tożsamością, oraz kontroli dostępu. Choć omówiliśmy wiele koncepcji w tym poście, wciąż istnieje wiele niuansów do uwzględnienia przy implementacji takiego systemu. Jeśli jesteś zainteresowany dalszym pogłębianiem wiedzy, możesz przeglądać naszego bloga w poszukiwaniu bardziej szczegółowych artykułów.