OpenID Connect: autoryzacja wewnętrznych aplikacji od rozwiązania własnego do standardu

Kilka miesięcy temu zajmowałem się implementacją serwera OpenID Connect do zarządzania dostępem do setek naszych wewnętrznych aplikacji. Od własnych rozwiązań, które były wygodne na mniejszych skalach, przeszliśmy do ogólnosystemowego standardu. Dostęp przez centralny serwis znacznie upraszcza monotonne operacje, obniża koszty realizacji autoryzacji, umożliwia znalezienie wielu gotowych rozwiązań i eliminuje ból głowy przy opracowywaniu nowych. W tym artykule opowiem o tym przejściu i o trudnościach, które napotkaliśmy.

OpenID Connect: autoryzacja wewnętrznych aplikacji od rozwiązania własnego do standardu

Dawno temu… Jak to wszystko się zaczęło

Kilka lat temu, gdy wewnętrznych aplikacji zrobiło się zbyt dużo do ręcznego zarządzania, napisaliśmy aplikację do kontrolowania dostępu w firmie. Była to prosta aplikacja Rails, która łączyła się z bazą danych zawierającą informacje o pracownikach, w której konfigurowano dostęp do różnych funkcji. Wtedy także uruchomiliśmy pierwsze SSO, które opierało się na weryfikacji tokenów po stronie klienta i serwera autoryzacji; token był przesyłany w szyfrowanej formie z kilkoma parametrami i weryfikowany na serwerze autoryzacji. Nie był to najwygodniejszy wariant, ponieważ w każdej wewnętrznej aplikacji trzeba było opisywać spory rozdział logiki, a bazy pracowników wcale nie synchronizowały się z serwerem autoryzacji.

Po pewnym czasie postanowiliśmy uprościć zadanie centralnej autoryzacji. SSO przeniesiono na balansator. Z pomocą OpenResty na Lua dodaliśmy szablon, który weryfikował tokeny, znał, do której aplikacji wysyłane jest zapytanie i mógł sprawdzić, czy istnieje do niej dostęp. Takie podejście znacznie uprościło kontrolę dostępu do wewnętrznych aplikacji - w kodzie każdej aplikacji nie trzeba było opisywać dodatkowej logiki. W rezultacie zamknęliśmy ruch zewnętrzny, a sama aplikacja nic nie wiedziała o autoryzacji.

Jednak jeden z problemów pozostał nierozwiązany. Co zrobić z aplikacjami, które potrzebują informacji o pracownikach? Można było napisać API dla usługi autoryzacji, ale wtedy trzeba by było dodać dodatkową logikę dla każdej takiej aplikacji. Poza tym chcieliśmy pozbyć się zależności od naszej własnej aplikacji, która w przyszłości miała być przenieść na Open Source, od naszego wewnętrznego serwisu autoryzacji. O tym opowiemy innym razem. Rozwiązaniem obu problemów było OAuth.

Do ogólnie przyjętych standardów

OAuth to zrozumiały, ogólnie przyjęty standard autoryzacji, ale ponieważ jego funkcjonalność jest niewystarczająca, zaczęto rozważać również OpenID Connect (OIDC). OIDC jest trzecią implementacją otwartego standardu uwierzytelniania, która powstała jako rozszerzenie protokołu OAuth 2.0 (otwarty protokół autoryzacji). Takie rozwiązanie zamyka problem braku danych o końcowym użytkowniku, a także umożliwia zmianę dostawcy autoryzacji.

Jednak nie zdecydowaliśmy się na konkretnego dostawcę i postanowiliśmy dodać integrację z OIDC do naszego istniejącego serwera autoryzacji. Na korzyść tego rozwiązania przemawia to, że OIDC jest bardzo elastyczny w zakresie autoryzacji końcowego użytkownika. W ten sposób powstała możliwość wdrożenia wsparcia OIDC na naszym obecnym serwerze autoryzacji.

OpenID Connect: autoryzacja wewnętrznych aplikacji od rozwiązania własnego do standardu

Nasza droga do realizacji własnego serwera OIDC

1) Przygotowaliśmy dane w odpowiednim formacie

Aby zintegrować OIDC, należy dostosować aktualne dane o użytkownikach do formatu zrozumiałego dla standardu. W OIDC nazywa się to Claims. Krey ma na celu zakończenie pól w bazie danych użytkowników (name, email, phone itd.). Istnieje standardowa lista krey, a wszystko, co nie wchodzi w ten wykaz, uważa się za niestandardowe. Dlatego pierwszym aspektem, na który należy zwrócić uwagę, jeśli zechcesz wybrać istniejącego dostawcę OIDC – jest możliwość łatwej personalizacji nowych krey.

Grupa krey jest łączona w następujące podzbiorów – Scope. Podczas autoryzacji żąda się dostępu nie do konkretnych krey, ale właśnie do zakresów, nawet jeśli część krey z zakresu jest zbędna.

2) Zrealizowaliśmy potrzebne granty

Następnym etapem integracji OIDC jest wybór i implementacja typów autoryzacji, zwanych grantami. Wybrany grant będzie decydować o dalszym scenariuszu interakcji wybranego zastosowania z serwerem autoryzacyjnym. Przykładowy schemat wyboru odpowiedniego grantu przedstawiono na poniższym rysunku.

OpenID Connect: autoryzacja wewnętrznych aplikacji od rozwiązania własnego do standardu

Dla naszej pierwszej aplikacji użyliśmy najczęściej wykorzystywanego grantu – Authorization Code. Jego odróżniającą cechą od innych jest to, że jest to proces trzyetapowy, tj. przechodzi dodatkową weryfikację. Najpierw użytkownik składa prośbę o zezwolenie na autoryzację, otrzymuje token – Authorization Code, następnie z tym tokenem, niczym z biletem na przejazd, prosi o token dostępu. Cała główna interakcja w tym scenariuszu autoryzacji opiera się na przekierowaniach między aplikacją a serwerem autoryzacyjnym. Więcej informacji na temat tego grantu można znaleźć. tutaj.

OAuth opiera się na koncepcji, że tokeny dostępu uzyskane po autoryzacji powinny być tymczasowe i zmieniać się, wskazane jest, co średnio 10 minut. Grant Authorization Code jest trzyetapową weryfikacją przez przekierowania, co 10 minut wykonywanie takiego kroku, szczerze mówiąc, nie jest najbardziej przyjemnym zajęciem dla oka. W celu rozwiązania tego problemu istnieje kolejny grant – Refresh Token, który także wykorzystaliśmy. Tutaj jest prościej. Podczas weryfikacji z innego grantu, oprócz głównego tokena dostępu, wydawany jest jeszcze jeden – Refresh Token, który można użyć tylko raz, a jego czas życia zazwyczaj jest znacznie dłuższy. Z tym Refresh Tokenem, gdy upłynie TTL (Time to Live) głównego tokena dostępu, wniosek o nowy token dostępu zostanie wysłany na endpoint już innego grantu. Użyty Refresh Token natychmiast się zeruje. Taka weryfikacja jest dwuetapowa i może być wykonana w tle, niezauważalnie dla użytkownika.

3) Skonfigurowano formaty wyjściowe danych użytkownika

Po zrealizowaniu wybranych grantów i zapewnieniu autoryzacji warto wspomnieć o uzyskiwaniu danych o użytkowniku końcowym. W OIDC istnieje osobny endpoint do tego celu, na którym można zażądać danych o użytkownikach, dysponując aktualnym tokenem dostępu. Jeśli dane użytkownika nie zmieniają się tak często, a konieczność ich pozyskania występuje wielokrotnie, warto rozważyć rozwiązanie w postaci tokenów JWT. Tokeny te są również wspierane przez standard. Sam token JWT składa się z trzech części: header (informacje o tokenie), payload (wszystkie potrzebne dane) oraz signature (podpis, token jest podpisywany przez serwer i później można zweryfikować źródło jego podpisu).

W implementacji OIDC token JWT nazywa się id_token. Można go zażądać razem z tradycyjnym tokenem dostępu, a jedyne, co pozostaje, to zweryfikować podpis. Serwer autoryzacji ma do tego osobny endpoint z zestawem kluczy publicznych w formacie JWK. Mówiąc o tym, warto wspomnieć, że istnieje jeszcze jeden endpoint, który na podstawie standardu RFC5785 odzwierciedla aktualną konfigurację serwera OIDC. Zawiera on wszystkie adresy endpointów (w tym adres zestawu kluczy publicznych wykorzystywanych do podpisu), wspierane roszczenia i zakresy, stosowane metody szyfrowania, wspierane granty itd.

Na przykład w Google:

{
 "issuer": "https://accounts.google.com",
 "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
 "device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
 "token_endpoint": "https://oauth2.googleapis.com/token",
 "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
 "revocation_endpoint": "https://oauth2.googleapis.com/revoke",
 "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
 "response_types_supported": [
  "code",
  "token",
  "id_token",
  "code token",
  "code id_token",
  "token id_token",
  "code token id_token",
  "none"
 ],
 "subject_types_supported": [
  "public"
 ],
 "id_token_signing_alg_values_supported": [
  "RS256"
 ],
 "scopes_supported": [
  "openid",
  "email",
  "profile"
 ],
 "token_endpoint_auth_methods_supported": [
  "client_secret_post",
  "client_secret_basic"
 ],
 "claims_supported": [
  "aud",
  "email",
  "email_verified",
  "exp",
  "family_name",
  "given_name",
  "iat",
  "iss",
  "locale",
  "name",
  "picture",
  "sub"
 ],
 "code_challenge_methods_supported": [
  "plain",
  "S256"
 ],
 "grant_types_supported": [
  "authorization_code",
  "refresh_token",
  "urn:ietf:params:oauth:grant-type:device_code",
  "urn:ietf:params:oauth:grant-type:jwt-bearer"
 ]
}

Dzięki id_tokenowi można przesyłać wszystkie potrzebne klauzule w payload tokena i nie trzeba za każdym razem zwracać się do serwera autoryzacji o dane użytkownika. Wadą tego podejścia jest to, że zmiany danych użytkownika z serwera nie przychodzą od razu, lecz wraz z nowym tokenem dostępu.

Wnioski z realizacji

Tak więc, po wdrożeniu własnego serwera OIDC i skonfigurowaniu połączeń z nim po stronie aplikacji, rozwiązaliśmy problem przesyłania informacji o użytkownikach.
Ponieważ OIDC to otwarty standard, mieliśmy możliwość wyboru istniejącego dostawcy lub wdrożenia serwera. Próbowaliśmy Keycloak, który okazał się bardzo wygodny w konfiguracji, po ustawieniach i zmianie konfiguracji połączeń po stronie aplikacji jest gotowy do pracy. Po stronie aplikacji pozostaje tylko zmienić konfiguracje połączeń.

Mówiąc o istniejących rozwiązaniach

W ramach naszej organizacji jako pierwszy serwer OIDC zbudowaliśmy własną implementację, która była rozwijana w miarę potrzeb. Po dokładnym zbadaniu innych gotowych rozwiązań, można powiedzieć, że to kontrowersyjny temat. Na korzyść wdrożenia własnego serwera przemawiały obawy ze strony dostawców o brak niezbędnych funkcjonalności, a także istnienie starego systemu, w którym były różne niestandardowe autoryzacje dla niektórych usług i przechowywano już dość dużo danych o pracownikach. Jednak w gotowych realizacjach istnieją udogodnienia do integracji. Na przykład w Keycloak jest własny system zarządzania użytkownikami, a dane są w nim przechowywane, a przeniesienie swoich użytkowników tam nie stanowi dużego trudu. W tym celu w Keycloak dostępne jest API, które umożliwia w pełni realizację wszystkich niezbędnych działań związanych z transferem.

Jeszcze jednym przykładem certyfikowanej, interesującej, moim zdaniem, realizacji jest Ory Hydra. Jest interesująca tym, że składa się z różnych komponentów. Do integracji będziesz musiał połączyć swój serwis zarządzania użytkownikami z ich serwisem autoryzacji i rozwijać w miarę potrzeby.

Keycloak i Ory Hydra to nie jedyne gotowe rozwiązania. Najlepiej dobierać certyfikowane implementacje OpenID Foundation. Zwykle takie rozwiązania mają symbol certyfikacji OpenID.

OpenID Connect: autoryzacja wewnętrznych aplikacji od rozwiązania własnego do standardu

Nie zapominaj o istniejących płatnych dostawcach, jeśli nie chcesz utrzymywać swojego serwera OIDC. Obecnie jest wiele dobrych opcji.

Co dalej

W najbliższej przyszłości planujemy zamknąć ruch do wewnętrznych usług w inny sposób. Zamierzamy przenieść nasze obecne SSO na load balancerze z wykorzystaniem OpenResty na proxy, które opiera się na OAuth. Istnieje już wiele gotowych rozwiązań, na przykład:
github.com/bitly/oauth2_proxy
github.com/ory/oathkeeper
github.com/keycloak/keycloak-gatekeeper

Dodatkowe materiały

jwt.io – dobry serwis do weryfikacji tokenów JWT
openid.net/developers/certified — lista certyfikowanych implementacji OIDC

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster