Token-basierte REST-API-Authentifizierung

122

Ich entwickle eine REST-API, die eine Authentifizierung erfordert. Da die Authentifizierung selbst über einen externen Webservice über HTTP erfolgt, habe ich argumentiert, dass wir Token ausgeben würden, um zu vermeiden, dass der Authentifizierungsdienst wiederholt aufgerufen wird. Was mich ordentlich zu meiner ersten Frage bringt:

Ist dies wirklich besser, als nur von Clients zu verlangen, dass sie bei jeder Anforderung HTTP Basic Auth verwenden und Aufrufe an den serverseitigen Authentifizierungsdienst zwischenspeichern?

Die Basic Auth-Lösung bietet den Vorteil, dass kein vollständiger Roundtrip zum Server erforderlich ist, bevor mit der Anforderung von Inhalten begonnen werden kann. Tokens können möglicherweise flexibler sein (dh nur Rechte für bestimmte Ressourcen oder Aktionen gewähren), aber dies scheint für den OAuth-Kontext angemessener zu sein als mein einfacherer Anwendungsfall.

Derzeit werden Token wie folgt erworben:

curl -X POST localhost/token --data "api_key=81169d80...
                                     &verifier=2f5ae51a...
                                     &timestamp=1234567
                                     &user=foo
                                     &pass=bar"

Die api_key, timestampund verifierwerden von allen Anfragen erforderlich. Der "Verifizierer" wird zurückgegeben von:

sha1(timestamp + api_key + shared_secret)

Meine Absicht ist es, nur Anrufe von bekannten Teilnehmern zuzulassen und zu verhindern, dass Anrufe wörtlich wiederverwendet werden.

Ist das gut genug Underkill? Overkill?

Mit einem Token in der Hand können Kunden Ressourcen erwerben:

curl localhost/posts?api_key=81169d80...
                    &verifier=81169d80...
                    &token=9fUyas64...
                    &timestamp=1234567

Für den einfachsten möglichen Anruf scheint dies schrecklich ausführlich zu sein. Wenn man bedenkt, dass der shared_secretWille in (mindestens) eine iOS-Anwendung eingebettet wird, von der ich annehmen würde, dass sie extrahiert werden kann, bietet dies überhaupt etwas, das über ein falsches Sicherheitsgefühl hinausgeht?

Cantlin
quelle
2
Anstatt sha1 (Zeitstempel + api_key + shard_secret) zu verwenden, sollten Sie hmac (shared_secret, timpestamp + api_key) für ein besseres Sicherheits-Hashing verwenden. En.wikipedia.org/wiki/Hash-based_message_authentication_code
Miguel A. Carrasco
@ MiguelA.Carrasco Und in scheint 2017 der Konsens zu bestehen, dass bCrypt das neue Hashing-Tool ist.
Shawn

Antworten:

94

Lassen Sie mich alles trennen und jedes Problem isoliert angehen:

Authentifizierung

Für die Authentifizierung hat baseauth den Vorteil, dass es sich um eine ausgereifte Lösung auf Protokollebene handelt. Das bedeutet viel Probleme, die später auftreten könnten, bereits für Sie gelöst sind. Mit BaseAuth wissen Benutzeragenten beispielsweise, dass das Kennwort ein Kennwort ist, sodass sie es nicht zwischenspeichern.

Auth Server laden

Wenn Sie dem Benutzer ein Token ausgeben, anstatt die Authentifizierung auf Ihrem Server zwischenzuspeichern, gehen Sie immer noch genauso vor: Zwischenspeichern von Authentifizierungsinformationen. Der einzige Unterschied besteht darin, dass Sie die Verantwortung für das Caching dem Benutzer übertragen. Dies scheint für den Benutzer unnötige Arbeit ohne Gewinne zu sein. Ich empfehle daher, dies auf Ihrem Server transparent zu handhaben, wie Sie vorgeschlagen haben.

Übertragungssicherheit

Wenn eine SSL-Verbindung verwendet werden kann, ist die Verbindung sicher *. Um eine versehentliche Mehrfachausführung zu verhindern, können Sie mehrere URLs filtern oder Benutzer auffordern, eine zufällige Komponente ("nonce") in die URL aufzunehmen.

url = username:[email protected]/api/call/nonce

Wenn dies nicht möglich ist und die übertragenen Informationen nicht geheim sind, empfehle ich, die Anforderung mit einem Hash zu sichern, wie Sie im Token-Ansatz vorgeschlagen haben. Da der Hash die Sicherheit bietet, können Sie Ihre Benutzer anweisen, den Hash als Baseauth-Passwort anzugeben. Um die Robustheit zu verbessern, empfehle ich, anstelle des Zeitstempels eine zufällige Zeichenfolge als "Nonce" zu verwenden, um Wiederholungsangriffe zu verhindern (zwei legitime Anforderungen können in derselben Sekunde gestellt werden). Anstatt separate Felder "Shared Secret" und "API-Schlüssel" bereitzustellen, können Sie einfach den API-Schlüssel als gemeinsames Geheimnis verwenden und dann ein Salz verwenden, das sich nicht ändert, um Angriffe auf Regenbogentabellen zu verhindern. Das Feld für den Benutzernamen scheint auch ein guter Ort zu sein, um das Nonce zu platzieren, da es Teil der Authentifizierung ist. Jetzt haben Sie einen sauberen Anruf wie folgt:

nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:[email protected]/api/call

Es ist wahr, dass dies ein bisschen mühsam ist. Dies liegt daran, dass Sie keine Lösung auf Protokollebene (wie SSL) verwenden. Daher ist es möglicherweise eine gute Idee, Benutzern eine Art SDK bereitzustellen, damit sie es zumindest nicht selbst durchgehen müssen. Wenn Sie es auf diese Weise tun müssen, finde ich die Sicherheitsstufe angemessen (genau richtig töten).

Sichere geheime Aufbewahrung

Es kommt darauf an, wen Sie vereiteln wollen. Wenn Sie verhindern, dass Personen mit Zugriff auf das Telefon des Benutzers Ihren REST-Service im Namen des Benutzers verwenden, ist es eine gute Idee, eine Art Schlüsselring-API auf dem Zielbetriebssystem zu finden und das SDK (oder den Implementierer) das zu speichern Schlüssel dort. Wenn dies nicht möglich ist, können Sie es zumindest etwas schwieriger machen, das Geheimnis zu erlangen, indem Sie es verschlüsseln und die verschlüsselten Daten und den Verschlüsselungsschlüssel an verschiedenen Orten speichern.

Wenn Sie versuchen, andere Softwareanbieter davon abzuhalten, Ihren API-Schlüssel zu erhalten, um die Entwicklung alternativer Clients zu verhindern, wird fast nur der Ansatz zum separaten Verschlüsseln und Speichern verwendet funktioniert . Dies ist Whitebox-Krypto, und bis heute hat niemand eine wirklich sichere Lösung für Probleme dieser Klasse gefunden. Das Mindeste, was Sie tun können, ist, für jeden Benutzer einen einzelnen Schlüssel auszugeben, damit Sie missbrauchte Schlüssel verbieten können.

(*) BEARBEITEN: SSL-Verbindungen sollten nicht mehr als sicher angesehen werden, ohne dass zusätzliche Schritte zur Überprüfung erforderlich sind.

cmc
quelle
Danke cmc, alle guten Punkte und gute Denkanstöße. Am Ende habe ich einen Token / HMAC-Ansatz gewählt, der dem oben beschriebenen ähnelt, ähnlich dem S3-REST-API- Authentifizierungsmechanismus.
Cantlin
Wenn Sie das Token auf dem Server zwischenspeichern, stimmt es dann nicht im Wesentlichen mit der guten alten Sitzungs-ID überein? Die Sitzungs-ID ist von kurzer Dauer und wird auch an den schnellen Cache-Speicher angehängt (sofern Sie ihn implementieren), um zu vermeiden, dass Ihre Datenbank bei jeder Anforderung getroffen wird. True RESTful & Stateless Design sollte keine Sitzungen enthalten. Wenn Sie jedoch ein Token als ID verwenden und dann immer noch auf die Datenbank zugreifen, ist es dann nicht besser, stattdessen nur die Sitzungs-ID zu verwenden? Alternativ können Sie JSON-Web-Token verwenden, die verschlüsselte oder signierte Informationen für die gesamten Sitzungsdaten enthalten, um ein echtes zustandsloses Design zu erzielen.
JustAMartin
16

Eine reine RESTful-API sollte die zugrunde liegenden Protokollstandardfunktionen verwenden:

  1. Für HTTP sollte die RESTful-API den vorhandenen HTTP-Standardheadern entsprechen. Das Hinzufügen eines neuen HTTP-Headers verstößt gegen die REST-Prinzipien. Erfinden Sie das Rad nicht neu, sondern verwenden Sie alle Standardfunktionen in HTTP / 1.1-Standards - einschließlich Statusantwortcodes, Headern usw. RESTFul-Webdienste sollten die HTTP-Standards nutzen und sich darauf verlassen.

  2. RESTful Services müssen zustandslos sein. Alle Tricks, wie die tokenbasierte Authentifizierung, die versucht, sich den Status früherer REST-Anforderungen auf dem Server zu merken, verstoßen gegen die REST-Prinzipien. Auch dies ist ein MUSS; Wenn Ihr Webserver kontextbezogene Informationen zu Anforderungen und Antworten auf dem Server speichert, um eine Sitzung auf dem Server einzurichten, ist Ihr Webdienst NICHT zustandslos. Und wenn es NICHT staatenlos ist, ist es NICHT RESTFul.

Fazit: Für Authentifizierungs- / Autorisierungszwecke sollten Sie den HTTP-Standardautorisierungsheader verwenden. Das heißt, Sie sollten den HTTP-Autorisierungs- / Authentifizierungsheader in jeder nachfolgenden Anforderung hinzufügen, die authentifiziert werden muss. Die REST-API sollte den Standards des HTTP-Authentifizierungsschemas entsprechen. Die Einzelheiten zur Formatierung dieses Headers sind in den RFC 2616 HTTP 1.1-Standards - Abschnitt 14.8 Autorisierung von RFC 2616 und in der RFC 2617 HTTP-Authentifizierung: Standard- und Digest-Zugriffsauthentifizierung definiert .

Ich habe einen RESTful-Service für die Cisco Prime Performance Manager-Anwendung entwickelt. Google - Suche für das REST - API Dokument , dass ich für diese Anwendung , um weitere Informationen über RESTful API Compliance schrieb hier . In dieser Implementierung habe ich mich für das HTTP-Autorisierungsschema "Basic" entschieden. - Überprüfen Sie Version 1.5 oder höher dieses REST-API-Dokuments und suchen Sie im Dokument nach Autorisierung.

Rubens Gomes
quelle
8
"Das Hinzufügen eines neuen HTTP-Headers verstößt gegen die REST-Prinzipien." Wie? Und wenn Sie schon dabei sind, können Sie so freundlich erklären, was genau der Unterschied (in Bezug auf Prinzipien) zwischen einem Passwort, das nach einer bestimmten Zeit abläuft, und einem Token, das nach einer bestimmten Zeit abläuft, ist.
Ein besserer Oliver
6
Benutzername + Passwort ist ein Token (!), Das bei jeder Anfrage zwischen einem Client und einem Server ausgetauscht wird. Dieses Token wird auf dem Server verwaltet und hat eine Lebensdauer. Wenn das Passwort abläuft, muss ich ein neues erwerben. Sie scheinen "Token" mit "Serversitzung" zu verknüpfen, aber das ist eine ungültige Schlussfolgerung. Es ist sogar irrelevant, weil es ein Implementierungsdetail wäre. Ihre Klassifizierung von anderen Token als Benutzername / Passwort als statusbehaftet ist rein künstlich, imho.
Ein besserer Oliver
1
Ich denke, Sie sollten motivieren, warum Sie eine Implementierung mit RESTful über die Basisauthentifizierung durchführen sollten, die Teil der ursprünglichen Frage ist. Vielleicht könnten Sie auch auf einige gute Beispiele mit Code verweisen. Als Anfänger in diesem Fach scheint die Theorie mit vielen guten Ressourcen klar genug zu sein, aber die Implementierungsmethode ist es nicht und die Beispiele sind verworren. Ich finde es frustrierend, dass es anscheinend benutzerdefinierte Codierung erfordert, um etwas rechtzeitig zu implementieren, was tausende Male getan wurde.
JPK
13
-1 "Alle Tricks wie die tokenbasierte Authentifizierung, die versuchen, sich den Status früherer REST-Anforderungen auf dem Server zu merken, verstoßen gegen die REST-Prinzipien." Die tokenbasierte Authentifizierung hat nichts mit dem Status früherer REST-Anforderungen zu tun und verletzt nicht die Statuslosigkeit von REST .
Kerem Baydoğan
1
Demnach stellen JSON-Web-Token eine REST-Verletzung dar, weil sie den Status des Benutzers (Ansprüche) speichern können. Wie auch immer, ich ziehe es vor, REST zu verletzen und eine gute alte Sitzungs-ID als "Token" zu verwenden, aber die anfängliche Authentifizierung wird mit Benutzername + Pass durchgeführt, signiert oder verschlüsselt mit einem gemeinsamen geheimen und sehr kurzlebigen Zeitstempel (daher schlägt dies fehl, wenn jemand versucht, die Wiedergabe durchzuführen Das). In einer "unternehmensorientierten" Anwendung ist es schwierig, Sitzungsvorteile wegzuwerfen (zu vermeiden, dass die Datenbank für einige Daten aufgerufen wird, die in fast jeder Anforderung benötigt werden), daher müssen wir manchmal echte Staatenlosigkeit opfern.
JustAMartin
2

Im Web basiert ein Stateful-Protokoll darauf, dass bei jeder Anforderung ein temporäres Token zwischen einem Browser und einem Server (über Cookie-Header oder URI-Umschreibung) ausgetauscht wird. Dieses Token wird normalerweise auf der Serverseite erstellt. Es handelt sich um undurchsichtige Daten mit einer bestimmten Lebensdauer und dem alleinigen Zweck, einen bestimmten Webbenutzeragenten zu identifizieren. Das heißt, das Token ist temporär und wird zu einem STAAT, den der Webserver während der Dauer dieser Konversation im Auftrag eines Client-Benutzeragenten verwalten muss. Daher ist die Kommunikation unter Verwendung eines Tokens auf diese Weise STATEFUL. Und wenn die Konversation zwischen Client und Server STATEFUL ist, ist sie nicht RESTful.

Der Benutzername / das Kennwort (gesendet im Authorization-Header) wird normalerweise in der Datenbank gespeichert, um einen Benutzer zu identifizieren. Manchmal kann der Benutzer eine andere Anwendung bedeuten. Der Benutzername / das Passwort lautet jedoch NIEMALS einen bestimmten Web - Client - User - Agenten identifizieren soll. Die Konversation zwischen einem Webagenten und einem Server basierend auf der Verwendung des Benutzernamens / Kennworts im Autorisierungsheader (nach der HTTP-Basisautorisierung) ist STATELESS, da das Webserver-Frontend keine STATE- Informationen erstellt oder verwaltet für einen bestimmten Web-Client-Benutzeragenten. Und basierend auf meinem Verständnis von REST besagt das Protokoll eindeutig, dass die Konversation zwischen Clients und Server STATELESS sein sollte. Wenn wir also einen echten RESTful-Service haben möchten, sollten wir im Authorization-Header für jeden einzelnen Aufruf den Benutzernamen / das Passwort (siehe RFC, der in meinem vorherigen Beitrag erwähnt wurde) verwenden, NICHT eine Art Sension-Token (z. B. Sitzungstoken, die auf Webservern erstellt wurden , OAuth-Token, die auf Autorisierungsservern erstellt wurden usw.).

Ich verstehe, dass mehrere angerufene REST-Anbieter Token wie OAuth1- oder OAuth2-Akzeptiertoken verwenden, die in HTTP-Headern als "Authorization: Bearer" übergeben werden. Es scheint mir jedoch, dass die Verwendung dieser Token für RESTful-Dienste die wahre STATELESS-Bedeutung verletzen würde, die REST umfasst. weil diese Token nur vorübergehend sind Daten sind, die auf der Serverseite erstellt / verwaltet werden, um einen bestimmten Webclient-Benutzeragenten für die gültige Dauer einer Webclient / Server-Konversation zu identifizieren. Daher sollte jeder Dienst, der diese OAuth1 / 2-Token verwendet, nicht als REST bezeichnet werden, wenn wir uns an die TRUE-Bedeutung eines STATELESS-Protokolls halten möchten.

Rubens

Rubens Gomes
quelle