OpenID Connect: Autorisierung interner Anwendungen vom Eigenbau zum Standard

Vor einigen Monaten habe ich einen OpenID Connect Server zur Verwaltung des Zugangs zu Hunderten unserer internen Anwendungen implementiert. Von unseren eigenen Lösungen, die für kleinere Maßstäbe geeignet waren, haben wir auf einen allgemein anerkannten Standard umgestellt. Der Zugang über einen zentralen Dienst vereinfacht die monotonen Operationen erheblich, reduziert die Kosten für die Implementierung von Authorisierungen, ermöglicht das Finden vieler fertiger Lösungen und erspart Kopfzerbrechen bei der Entwicklung neuer Lösungen. In diesem Artikel werde ich über diesen Übergang und die Herausforderungen berichten, die wir dabei bewältigt haben.

OpenID Connect: Autorisierung interner Anwendungen vom Eigenbau zum Standard

Es war einmal… Wie alles begann

Vor einigen Jahren, als es zu viele interne Anwendungen gab, um sie manuell zu verwalten, haben wir eine Anwendung zur Zugangskontrolle innerhalb des Unternehmens entwickelt. Es war eine einfache Rails-Anwendung, die sich mit einer Datenbank über die Mitarbeiterinformationen verband, in der der Zugang zu verschiedenen Funktionalitäten eingestellt wurde. Damals haben wir das erste SSO eingeführt, das auf der Überprüfung von Tokens zwischen dem Client und dem Autorisierungsserver basierte; das Token wurde verschlüsselt mit mehreren Parametern übertragen und auf dem Autorisierungsserver abgeglichen. Diese Lösung war nicht die praktischste, da für jede interne Anwendung eine nicht unerhebliche Logikschicht beschrieben werden musste und die Mitarbeiterdaten nicht mit dem Autorisierungsserver synchronisiert wurden.

Nach einiger Zeit haben wir beschlossen, die Aufgabe der zentralen Authorisierung zu vereinfachen. Das SSO wurde auf einen Load Balancer umgestellt. Mithilfe von OpenResty auf Lua haben wir eine Vorlage hinzugefügt, die die Tokens überprüfte, wusste, auf welche Anwendung die Anfrage ging und prüfen konnte, ob Zugriff darauf bestand. Dieser Ansatz hat die Aufgabe der Zugangskontrolle für interne Anwendungen erheblich vereinfacht – im Code jeder Anwendung musste keine zusätzliche Logik mehr beschrieben werden. Letztendlich haben wir den externen Datenverkehr gesperrt, und die Anwendung selbst wusste nichts von der Authorisierung.

Jedoch bleibt eines der Probleme ungelöst. Was ist mit den Anwendungen, die Informationen über Mitarbeiter benötigen? Man hätte eine API für den Autorisierungsdienst schreiben können, aber dann müsste man für jede dieser Anwendungen zusätzliche Logik hinzufügen. Außerdem wollten wir unsere Abhängigkeit von einer unserer selbst geschriebenen Anwendungen aufgeben, die in Zukunft auf die Umstellung auf OpenSource ausgerichtet ist, und von unserem internen Autorisierungsserver. Darüber werden wir irgendwann anders berichten. Die Lösung für beide Probleme war OAuth.

Zu allgemein anerkannten Standards

OAuth ist ein verständlicher, allgemein anerkannter Standard für die Autorisierung, aber da seine Funktionalität allein nicht ausreicht, wurden sofort auch OpenID Connect (OIDC) in Betracht gezogen. An sich ist OIDC die dritte Implementierung eines offenen Authentifizierungsstandards, der als Erweiterung des OAuth 2.0-Protokolls (öffnenes Autorisierungsprotokoll) dient. Diese Lösung schließt das Problem des Fehlens von Informationen über den Endbenutzer und ermöglicht auch den Wechsel des Autorisierungsanbieters.

Wir haben jedoch keinen bestimmten Anbieter ausgewählt und beschlossen, die Integration von OIDC in unseren bestehenden Autorisierungsserver hinzuzufügen. Ein Grund für diese Entscheidung war die Flexibilität von OIDC in Bezug auf die Autorisierung des Endbenutzers. So war es möglich, die Unterstützung für OIDC auf unserem aktuellen Autorisierungsserver zu implementieren.

OpenID Connect: Autorisierung interner Anwendungen vom Eigenbau zum Standard

Unser Weg zur Umsetzung eines eigenen OIDC-Servers

1) Wir haben die Daten in die benötigte Form gebracht

Für die Integration von OIDC müssen die vorhandenen Benutzerdaten in ein für den Standard verständliches Format gebracht werden. In OIDC werden diese als Claims bezeichnet. Claims sind im Grunde die Endfelder in der Benutzerdatenbank (Name, E-Mail, Telefon usw.). Es gibt eine standardisierte Liste von Claims, und alles, was nicht in dieser Liste enthalten ist, wird als benutzerdefiniert betrachtet. Daher ist der erste Punkt, auf den man achten sollte, falls man einen bestehenden OIDC-Anbieter auswählen möchte, die Möglichkeit, neue Claims benutzerfreundlich anzupassen.

Eine Gruppe von Claims wird in das folgende Teilmenge – Scope – zusammengefasst. Bei der Autorisierung wird nicht auf bestimmte Claims zugegriffen, sondern auf die Scopes, auch wenn einige Claims aus dem Scope nicht benötigt werden.

2) Wir haben die benötigten Grants implementiert

Der nächste Teil der OIDC-Integration ist die Auswahl und Implementierung von Autorisierungsarten, den sogenannten Grant-Typen. Das gewählte Grant bestimmt das weitere Szenario der Interaktion der ausgewählten Anwendung mit dem Autorisierungsserver. Ein schematisches Beispiel zur Auswahl des richtigen Grants ist in der untenstehenden Abbildung dargestellt.

OpenID Connect: Autorisierung interner Anwendungen vom Eigenbau zum Standard

Für unsere erste Anwendung haben wir den gebräuchlichsten Grant verwendet – den Authorization Code. Der Unterschied zu anderen Grants besteht darin, dass er dreistufig ist, d.h. eine zusätzliche Überprüfung durchläuft. Zuerst beantragt der Benutzer die Autorisierungsberechtigung, erhält einen Token – den Authorization Code, und fragt dann mit diesem Token, ähnlich wie mit einem Fahrkarte, den Zugriffstoken an. Alle wesentlichen Interaktionen in diesem Autorisierungsszenario beruhen auf Weiterleitungen zwischen der Anwendung und dem Autorisierungsserver. Mehr Informationen zu diesem Grant finden Sie hier.

OAuth folgt dem Konzept, dass die nach der Autorisierung erhaltenen Zugriffstoken vorübergehend und idealerweise alle 10 Minuten gewechselt werden sollten. Der Grant Authorization Code ist eine dreistufige Überprüfung über Weiterleitungen, und alle 10 Minuten einen solchen Schritt durchzuführen, ist ehrlich gesagt nicht die angenehmste Beschäftigung. Zur Lösung dieses Problems gibt es einen weiteren Grant – den Refresh Token, den wir ebenfalls eingesetzt haben. Hier ist das Ganze einfacher. Während der Überprüfung mit einem anderen Grant wird zusätzlich zum Hauptzugriffstoken ein weiterer – der Refresh Token – ausgegeben, der nur einmal verwendet werden kann und dessen Lebensdauer in der Regel deutlich länger ist. Mit diesem Refresh Token, wenn die TTL (Time to Live) des Hauptzugriffstokens abläuft, erfolgt die Anfrage nach einem neuen Zugriffstoken bereits über den Endpoint eines anderen Grants. Der verwendete Refresh Token wird sofort zurückgesetzt. Diese Überprüfung ist zweistufig und kann im Hintergrund, unbemerkt vom Benutzer, durchgeführt werden.

3) Wir haben die Ausgabeformate für Benutzerdaten konfiguriert

Nachdem die ausgewählten Stipendien umgesetzt sind und die Autorisierung funktioniert, sollte man die Daten zum Endanwender erwähnen. Im OIDC gibt es einen eigenen Endpoint dafür, bei dem man mit seinem aktuellen Zugriffstoken und dessen Gültigkeit Benutzerdaten anfordern kann. Und wenn sich die Benutzerdaten nicht so oft ändern, man jedoch mehrfach auf die aktuellen Informationen zugreifen muss, kann man zu einer Lösung wie JWT-Token kommen. Diese Token werden ebenfalls durch den Standard unterstützt. Ein JWT-Token selbst besteht aus drei Teilen: header (Informationen über das Token), payload (alle benötigten Daten) und signature (die Signatur, das Token wird vom Server signiert und die Quelle der Signatur kann später überprüft werden).

In der Implementierung von OIDC wird das JWT-Token als id_token bezeichnet. Es kann zusammen mit dem regulären Zugangstoken angefordert werden und alles, was bleibt, ist die Überprüfung der Signatur. Der Autorisierungsserver hat dafür einen eigenen Endpoint mit einer Kombination aus öffentlichen Schlüsseln im Format JWK. Und dabei sollte erwähnt werden, dass es einen weiteren Endpoint gibt, der auf Grundlage des Standards RFC5785 die aktuelle Konfiguration des OIDC-Servers widerspiegelt. Dort sind alle Endpoint-Adressen (einschließlich der Adresse für die Schlüsselbundkombination, die für die Signatur verwendet wird), unterstützte Claims und Scopes, die verwendeten Verschlüsselungsalgorithmen, unterstützte Grants usw. enthalten.

Zum Beispiel in 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"
 ]
}

Durch den Einsatz des id_token können alle erforderlichen Ansprüche im Payload des Tokens übermittelt werden, ohne dass man jedes Mal den Authentifizierungsserver zur Abfrage von Benutzerdaten kontaktieren muss. Ein Nachteil dieses Ansatzes ist, dass Änderungen an den Benutzerdaten vom Server nicht sofort, sondern zusammen mit einem neuen Zugriffstoken erfolgen.

Ergebnisse der Implementierung

Nachdem wir unseren eigenen OIDC-Server implementiert und die Verbindungen auf der Anwendungsseite eingerichtet hatten, haben wir das Problem der Übertragung von Benutzerdaten gelöst.
Da OIDC ein offener Standard ist, hatten wir die Möglichkeit, zwischen einem bestehenden Anbieter oder der Implementierung eines eigenen Servers zu wählen. Wir haben Keycloak ausprobiert, welches sich als sehr benutzerfreundlich in der Konfiguration erwiesen hat, und nach der Anpassung und Änderung der Verbindungskonfigurationen auf der Anwendungsseite ist es betriebsbereit. Auf der Anwendungsseite müssen nur die Verbindungskonfigurationen geändert werden.

Wenn wir über bestehende Lösungen sprechen

Im Rahmen unserer Organisation haben wir als ersten OIDC-Server unsere eigene Implementierung zusammengestellt, die nach Bedarf erweitert wurde. Nach einer gründlichen Überprüfung anderer fertiger Lösungen kann gesagt werden, dass dies ein umstrittener Punkt ist. Für die Entscheidung, einen eigenen Server zu implementieren, sprachen Bedenken der Anbieter hinsichtlich fehlender notwendiger Funktionen sowie das Vorhandensein eines alten Systems, in dem verschiedene benutzerdefinierte Autorisierungen für einige Dienste vorhanden waren und bereits viele Daten über die Mitarbeiter gespeichert waren. Allerdings bieten die fertigen Implementierungen Annehmlichkeiten für die Integration. Zum Beispiel verfügt Keycloak über ein eigenes Benutzerverwaltungsystem, und die Daten werden direkt dort gespeichert, wobei es nicht viel Mühe erfordert, seine Benutzer dorthin zu übertragen. Hierzu bietet Keycloak eine API, die es ermöglicht, alle erforderlichen Aktionen für den Transfer vollständig durchzuführen.

Ein weiteres Beispiel für eine zertifizierte, interessante Implementierung ist Ory Hydra. Sie ist interessant, weil sie aus verschiedenen Komponenten besteht. Für die Integration benötigen Sie eine Verbindung Ihres Benutzerverwaltungssystems mit ihrem Authentifizierungsdienst und können es nach Bedarf erweitern.

Keycloak und Ory Hydra sind nicht die einzigen verfügbaren Lösungen. Am besten wählt man eine zertifizierte OpenID Foundation-Implementierung. Solche Lösungen haben in der Regel ein Symbol für die OpenID-Zertifizierung.

OpenID Connect: Autorisierung interner Anwendungen vom Eigenbau zum Standard

Vergessen Sie auch nicht die existierenden kostenpflichtigen Anbieter, falls Sie Ihren OIDC-Server nicht selbst betreiben möchten. Heute gibt es viele gute Optionen.

Was kommt als nächstes

In naher Zukunft planen wir, den Verkehr zu internen Services auf eine andere Weise zu steuern. Wir beabsichtigen, unser aktuelles SSO auf einem Load Balancer mithilfe von OpenResty auf einen Proxy zu migrieren, der auf OAuth basiert. Auch hier gibt es bereits viele fertige Lösungen, zum Beispiel:
github.com/bitly/oauth2_proxy
github.com/ory/oathkeeper
github.com/keycloak/keycloak-gatekeeper

Zusätzliche Materialien

jwt.io – ein guter Dienst zur Überprüfung von JWT-Token
openid.net/developers/certified — eine Liste zertifizierter Implementierungen von OIDC

Quelle: habr.com

60GB SSD 8Gb DDR4