REST-Authentifizierung und Offenlegen des API-Schlüssels

93

Ich habe über REST gelesen und es gibt viele Fragen zu SO darüber sowie zu vielen anderen Websites und Blogs. Obwohl ich diese spezielle Frage noch nie gestellt habe ... aus irgendeinem Grund kann ich mich nicht um dieses Konzept kümmern ...

Wenn ich eine RESTful-API erstelle und sie sichern möchte, ist eine der Methoden, die ich gesehen habe, die Verwendung eines Sicherheitstokens. Wenn ich andere APIs verwendet habe, gab es ein Token und ein gemeinsames Geheimnis ... macht Sinn. Was ich nicht verstehe ist, dass Anfragen an einen Rest-Service-Vorgang über Javascript (XHR / Ajax) gestellt werden, um zu verhindern, dass jemand dies mit etwas Einfachem wie FireBug (oder "Quelltext anzeigen" im Browser) und Kopieren Sie den API-Schlüssel und geben Sie sich dann mit dem Schlüssel und dem Geheimnis als diese Person aus?

tjans
quelle
Eine der Methoden, die ich gesehen habe, ist die Verwendung eines Sicherheitstokens . Es gibt wirklich viele Methoden. Haben Sie ein konkretes Beispiel. Ich denke, Sie werden verwirrt mit "REST" vs. "Javascript-API nur für registrierte Benutzer verfügbar machen" (ex Google Maps).
PeterMmm
1
Seit du vor fast 2 Jahren gefragt hast: Was hast du letztendlich selbst benutzt?
Arjan
Ich habe eigentlich nichts benutzt, sondern nur versucht, die Konzepte zu entwickeln. Der obige Kommentar von PeterMmm ist wahrscheinlich wahr ... ich hatte immer noch keine Notwendigkeit, irgendetwas davon umzusetzen, aber ich wollte mich verbessern ... danke für das Follow-up.
Tjans

Antworten:

22

Das API-Geheimnis wird nicht explizit übergeben. Das Geheimnis wird verwendet, um ein Zeichen der aktuellen Anforderung zu generieren. Auf der Serverseite generiert der Server das Zeichen nach demselben Vorgang. Wenn die beiden Zeichen übereinstimmen, wird die Anforderung erfolgreich authentifiziert - also nur das Zeichen wird durch die Anfrage geleitet, nicht das Geheimnis.

James.Xu
quelle
9
Also, wenn es nur das Zeichen ist, das übergeben wurde ... ist das nicht immer noch in Javascript verfügbar ... Wenn ich also über ihre API (von Javascript aufgerufen) ein Flimmerfoto auf meine Webseite setze und Sie meine Seite besuchen, sind Sie nicht da? t Ich stelle meinen API-Schlüssel jedem zur Verfügung, der meine Seite besucht.
Tjans
6
Ich glaube nicht, dass ich meine Frage richtig stelle ... wahrscheinlich ein Teil des Grundes, warum ich überhaupt nicht gefunden habe, wonach ich gesucht habe. Wenn ich meinen Ajax-Aufruf mache, beispielsweise mit jquery, muss ich den API-Schlüssel in den Ajax-Aufruf einbetten, damit er an den Server übergeben wird. An diesem Punkt kann jemand den API-Schlüssel sehen. Wenn ich das falsch verstehe, wie wird der API-Schlüssel mit der Anforderung gesendet, wenn er nicht in das Client-Skript eingebettet ist?
Tjans
4
Fazit: Vor der Verwendung eines Openapi / Restapi wird den Personen ein Apikey + Apisecret-Paar zugewiesen. Das Apikey + -Zeichen wird auf die Serverseite übertragen, um sicherzustellen, dass der Server weiß, wer die Anforderung stellt. Das Apisecret wird aus Sicherheitsgründen niemals auf die Serverseite übertragen .
James.Xu
7
Die Aussage von @ James.Xu, dass "Geheimnis verwendet wird, um ein Zeichen der aktuellen Anfrage zu generieren", ist also FALSCH! Weil der Client das Geheimnis nicht kennt, weil es unsicher wäre, es ihm zu senden (und woher sollte er das sonst wissen?) Das "Geheimnis", das technisch ein "privater Schlüssel" ist, wird NUR VOM SERVER verwendet (weil niemand sonst weiß es), ein Zeichen zu generieren, das mit dem Zeichen des Kunden verglichen werden soll. Die Frage: Welche Art von Daten werden mit dem 'API-Schlüssel' kombiniert, den niemand außer dem Client und dem Server kennt? Zeichen = api_key + was?
ACs
1
Du hast recht, @ACs. Selbst wenn beide Server (die Website und die API von Drittanbietern) dasselbe Geheimnis kennen, kann man keine Signatur auf dem Website-Server berechnen und dieses Ergebnis dann in HTML / JavaScript einfügen und dann vom Browser an die API weiterleiten lassen. Auf diese Weise könnte jeder andere Server HTML vom ersten Webserver anfordern, die Signatur aus der Antwort abrufen und diese im HTML-Code auf seiner eigenen Website verwenden. (Ich denke wirklich, der obige Beitrag beantwortet nicht die Frage, wie ein öffentlicher API-Schlüssel im HTML sicher sein kann.)
Arjan
61

Wir stellen eine API zur Verfügung, die Partner nur für Domains verwenden können, die sie bei uns registriert haben. Der Inhalt ist teilweise öffentlich (wird aber vorzugsweise nur in den uns bekannten Domains angezeigt), ist jedoch für unsere Benutzer größtenteils privat. So:

  • Um festzustellen, was angezeigt wird, muss unser Benutzer bei uns angemeldet sein, dies wird jedoch separat behandelt.

  • Um festzustellen, wo die Daten angezeigt werden, wird ein öffentlicher API-Schlüssel verwendet, um den Zugriff auf uns bekannte Domänen zu beschränken und vor allem um sicherzustellen, dass die privaten Benutzerdaten nicht für CSRF anfällig sind .

Dieser API-Schlüssel ist in der Tat für jeden sichtbar, wir authentifizieren unseren Partner nicht auf andere Weise und benötigen keinen REFERER . Trotzdem ist es sicher:

  1. Wenn unsere get-csrf-token.js?apiKey=abc123angefordert wird:

    1. Suchen Sie den Schlüssel abc123in der Datenbank und erhalten Sie eine Liste der gültigen Domänen für diesen Schlüssel.

    2. Suchen Sie nach dem CSRF-Validierungscookie. Wenn es nicht vorhanden ist, generieren Sie einen sicheren Zufallswert und fügen Sie ihn in ein Nur-HTTP- Sitzungscookie ein. Wenn das Cookie vorhanden war, rufen Sie den vorhandenen Zufallswert ab.

    3. Erstellen Sie ein CSRF-Token aus dem API-Schlüssel und dem Zufallswert aus dem Cookie und signieren Sie es . (Anstatt eine Liste der Token auf dem Server zu führen, signieren wir die Werte. Beide Werte sind im signierten Token lesbar, das ist in Ordnung.)

    4. Stellen Sie die Antwort so ein, dass sie nicht zwischengespeichert wird, fügen Sie das Cookie hinzu und geben Sie ein Skript wie folgt zurück:

      var apiConfig = apiConfig || {};
      if(document.domain === 'expected-domain.com' 
            || document.domain === 'www.expected-domain.com') {
      
          apiConfig.csrfToken = 'API key, random value, signature';
      
          // Invoke a callback if the partner wants us to
          if(typeof apiConfig.fnInit !== 'undefined') {
              apiConfig.fnInit();
          }
      } else {
          alert('This site is not authorised for this API key.');
      }

    Anmerkungen:

    • Dies verhindert nicht, dass ein serverseitiges Skript eine Anforderung vortäuscht, sondern stellt nur sicher, dass die Domäne übereinstimmt, wenn dies von einem Browser angefordert wird.

    • Die Same Origin Policy für JavaScript sorgt dafür , dass ein Browser nicht XHR (Ajax) verwenden können , laden und dann die JavaScript - Quelle zu überprüfen. Stattdessen kann ein normaler Browser ihn nur mit <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(oder einem dynamischen Äquivalent) laden und führt dann den Code aus. Natürlich sollte der Server nicht unterstützt Cross-Origin Resource Sharing noch JSONP für den generierten JavaScript.

    • Ein Browserskript kann den Wert von ändern, document.domainbevor das obige Skript geladen wird. Dieselbe Ursprungsrichtlinie ermöglicht jedoch nur die Verkürzung der Domäne durch Entfernen von Präfixen, z. B. das Umschreiben subdomain.example.comauf nur example.comoder myblog.wordpress.comauf wordpress.comoder in einigen Browsern sogar bbc.co.ukauf co.uk.

    • Wenn die JavaScript-Datei mit einem serverseitigen Skript abgerufen wird, erhält der Server auch das Cookie. Ein Server eines Drittanbieters kann den Browser eines Benutzers jedoch nicht dazu bringen, dieses Cookie unserer Domain zuzuordnen. Daher kann ein CSRF-Token und ein Validierungscookie, die mit einem serverseitigen Skript abgerufen wurden, nur von nachfolgenden serverseitigen Aufrufen verwendet werden, nicht in einem Browser. Solche serverseitigen Aufrufe enthalten jedoch niemals das Benutzer-Cookie und können daher nur öffentliche Daten abrufen. Dies sind die gleichen Daten, die ein serverseitiges Skript direkt von der Website des Partners entfernen könnte.

  2. Wenn sich ein Benutzer anmeldet, setzen Sie ein Benutzer-Cookie nach Ihren Wünschen. (Der Benutzer hat sich möglicherweise bereits angemeldet, bevor das JavaScript angefordert wurde.)

  3. Alle nachfolgenden API-Anforderungen an den Server (einschließlich GET- und JSONP-Anforderungen) müssen das CSRF-Token, das CSRF-Validierungscookie und (falls angemeldet) das Benutzercookie enthalten. Der Server kann nun bestimmen, ob der Anforderung vertraut werden soll:

    1. Das Vorhandensein eines gültigen CSRF-Tokens stellt sicher, dass das JavaScript aus der erwarteten Domäne geladen wurde , wenn es von einem Browser geladen wurde.

    2. Das Vorhandensein des CSRF-Tokens ohne das Validierungscookie weist auf eine Fälschung hin.

    3. Das Vorhandensein sowohl des CSRF-Tokens als auch des CSRF-Validierungscookies stellt nichts sicher: Dies kann entweder eine gefälschte serverseitige Anforderung oder eine gültige Anforderung von einem Browser sein. (Es kann sich nicht um eine Anfrage eines Browsers handeln, die von einer nicht unterstützten Domain stammt.)

    4. Das Vorhandensein des Benutzercookies stellt sicher, dass der Benutzer angemeldet ist, stellt jedoch nicht sicher, dass der Benutzer Mitglied des angegebenen Partners ist oder dass der Benutzer die richtige Website anzeigt.

    5. Das Vorhandensein des Benutzercookies ohne das CSRF-Validierungscookie weist auf eine Fälschung hin.

    6. Das Vorhandensein des Benutzer-Cookies stellt sicher, dass die aktuelle Anforderung über einen Browser erfolgt. (Angenommen, ein Benutzer würde seine Anmeldeinformationen nicht auf einer unbekannten Website eingeben, und vorausgesetzt, es ist uns egal, ob Benutzer ihre eigenen Anmeldeinformationen verwenden, um eine serverseitige Anfrage zu stellen.) Wenn wir auch das CSRF-Validierungscookie haben, war dies das CSRF-Validierungscookie auch über einen Browser empfangen. Als nächstes, wenn wir auch ein CSRF-Token mit einer gültigen Signatur haben, undDie Zufallszahl im CSRF-Validierungscookie stimmt mit der in diesem CSRF-Token überein. Das JavaScript für dieses Token wurde dann auch während derselben früheren Anforderung empfangen, bei der das CSRF-Cookie gesetzt wurde, und verwendet daher auch einen Browser. Dies impliziert dann auch, dass der obige JavaScript-Code ausgeführt wurde, bevor das Token gesetzt wurde, und dass zu diesem Zeitpunkt die Domäne für den angegebenen API-Schlüssel gültig war.

      Also: Der Server kann jetzt den API-Schlüssel vom signierten Token sicher verwenden.

    7. Wenn der Server der Anforderung zu irgendeinem Zeitpunkt nicht vertraut, wird ein 403 Forbidden zurückgegeben. Das Widget kann darauf reagieren, indem es dem Benutzer eine Warnung anzeigt.

Es ist nicht erforderlich, das CSRF-Validierungscookie zu signieren, da wir es mit dem signierten CSRF-Token vergleichen. Wenn Sie das Cookie nicht signieren, wird jede HTTP-Anforderung kürzer und die Serverüberprüfung etwas schneller.

Das generierte CSRF-Token ist unbegrenzt gültig, jedoch nur in Kombination mit dem Validierungscookie, also effektiv, bis der Browser geschlossen wird.

Wir könnten die Lebensdauer der Signatur des Tokens begrenzen. Wir könnten das CSRF-Validierungscookie löschen, wenn sich der Benutzer abmeldet, um die OWASP-Empfehlung zu erfüllen . Und um die Zufallszahl pro Benutzer nicht zwischen mehreren Partnern zu teilen, könnte man den API-Schlüssel zum Cookie-Namen hinzufügen. Aber selbst dann kann man das CSRF-Validierungscookie nicht einfach aktualisieren, wenn ein neues Token angefordert wird, da Benutzer möglicherweise dieselbe Site in mehreren Fenstern durchsuchen und ein einzelnes Cookie freigeben (das beim Aktualisieren in allen Fenstern aktualisiert wird, wonach das Das JavaScript-Token in den anderen Fenstern würde nicht mehr mit diesem einzelnen Cookie übereinstimmen.

Für diejenigen, die OAuth verwenden, siehe auch OAuth und clientseitige Widgets , von denen ich die JavaScript-Idee erhalten habe. Für die serverseitige Verwendung der API, bei der wir uns nicht auf den JavaScript-Code verlassen können, um die Domäne einzuschränken, verwenden wir geheime Schlüssel anstelle der öffentlichen API-Schlüssel.

Arjan
quelle
1
Wenn man CORS verwendet, kann man das vielleicht sicher erweitern. Anstelle der oben genannten Informationen teilt OPTIONSder Server einem Browser möglicherweise mit, welche Domänen zulässig sind (oder bricht die Anforderung ab) , wenn eine vorab geflogene Anforderung mit einem öffentlichen API-Schlüssel in der URL verarbeitet wird. Beachten Sie jedoch, dass für einige Anfragen keine vorab geflogene Anfrage erforderlich ist oder CORS überhaupt nicht verwendet wird und dass CORS IE8 + benötigt. Wenn für IE7 ein Flash-Fallback verwendet wird, kann möglicherweise eine gewisse Dynamik crossdomain.xmldazu beitragen, dasselbe zu erreichen. Wir haben CORS / Flash noch nicht ausprobiert.
Arjan
10

Diese Frage hat eine akzeptierte Antwort, aber nur um zu verdeutlichen, funktioniert die gemeinsame geheime Authentifizierung folgendermaßen:

  1. Der Client hat einen öffentlichen Schlüssel, der mit jedem geteilt werden kann, egal, also können Sie ihn in Javascript einbetten. Dies wird verwendet, um den Benutzer auf dem Server zu identifizieren.
  2. Der Server hat einen geheimen Schlüssel und dieses Geheimnis MUSS geschützt werden. Daher erfordert die Authentifizierung mit gemeinsam genutzten Schlüsseln, dass Sie Ihren geheimen Schlüssel schützen können. Ein öffentlicher Javascript-Client, der eine direkte Verbindung zu einem anderen Dienst herstellt, ist daher nicht möglich, da Sie einen Server-Vermittler benötigen, um das Geheimnis zu schützen.
  3. Der Server signiert die Anforderung mithilfe eines Algorithmus, der den geheimen Schlüssel enthält (der geheime Schlüssel ähnelt einem Salt), und vorzugsweise sendet ein Zeitstempel die Anforderung an den Dienst. Der Zeitstempel soll "Wiederholungs" -Angriffe verhindern. Eine Signatur einer Anfrage ist nur für ungefähr n gültig Sekunden . Sie können dies auf dem Server überprüfen, indem Sie den Zeitstempel-Header abrufen, der den Wert des Zeitstempels enthalten soll, der in der Signatur enthalten war. Wenn dieser Zeitstempel abgelaufen ist, schlägt die Anforderung fehl.
  4. Der Dienst erhält die Anfrage, die nicht nur die Signatur enthält, sondern auch alle Felder, die im Klartext signiert wurden.
  5. Der Dienst signiert dann die Anforderung auf die gleiche Weise mit dem gemeinsam genutzten geheimen Schlüssel und vergleicht die Signaturen.
chris
quelle
Richtig, aber Ihre Antwort legt den API-Schlüssel nicht offen. In einigen APIs ist der API-Schlüssel jedoch öffentlich sichtbar, und darum ging es bei der Frage: "Anforderungen an einen Restdienst [...] über Javascript (XHR / Ajax)" . (Die akzeptierte Antwort ist auch darüber falsch, ich fühle; Ihr Punkt 2 ist darüber klar, gut.)
Arjan
1

Ich nehme an, Sie meinen Sitzungsschlüssel, nicht API-Schlüssel. Dieses Problem wird vom http-Protokoll geerbt und als Sitzungsentführung bezeichnet . Die normale "Problemumgehung" besteht, wie auf jeder Website, darin, zu https zu wechseln.

Um den REST-Service sicher auszuführen, müssen Sie https und wahrscheinlich die Clientauthentifizierung aktivieren. Dies geht jedoch über die REST-Idee hinaus. REST spricht nie über Sicherheit.

PeterMmm
quelle
8
Ich meinte eigentlich den Schlüssel. Wenn ich mich richtig erinnere, übergeben Sie zur Verwendung einer API den API-Schlüssel und das Geheimnis an den Restdienst, um sich zu authentifizieren. Richtig? Ich weiß, sobald es über die Leitung geleitet wird, wird es mit SSL verschlüsselt, aber bevor es gesendet wird, ist dies durch den Client-Code, der es verwendet, perfekt sichtbar ...
tjans
1

Auf der Serverseite möchten Sie eine ablaufende Sitzungs-ID generieren, die beim Anmelden oder Anmelden an den Client zurückgesendet wird. Der Client kann diese Sitzungs-ID dann als gemeinsames Geheimnis verwenden, um nachfolgende Anforderungen zu signieren.

Die Sitzungs-ID wird nur einmal übergeben und MUSS über SSL erfolgen.

Siehe Beispiel hier

Verwenden Sie beim Signieren der Anforderung eine Nonce und einen Zeitstempel, um die Entführung von Sitzungen zu verhindern.

Iain Porter
quelle
1
Aber wie kann es zu einer Anmeldung kommen, wenn ein Dritter Ihre API verwendet? Wenn sich der Benutzer anmelden möchte, sind die Dinge einfach: Verwenden Sie einfach eine Sitzung? Wenn sich andere Websites bei Ihrer API authentifizieren müssen, hilft dies nicht. (Auch das riecht sehr nach Werbung für Ihr Blog.)
Arjan
1

Ich werde versuchen, die Frage im ursprünglichen Kontext zu beantworten. Die Frage lautet also: "Ist der geheime (API) Schlüssel sicher, in JavaScript platziert zu werden?"

Meiner Meinung nach ist es sehr unsicher, da es den Zweck der Authentifizierung zwischen den Systemen zunichte macht. Da der Schlüssel dem Benutzer zugänglich gemacht wird, kann der Benutzer Informationen abrufen, zu denen er nicht berechtigt ist. Denn in einer typischen Ruhephase basiert die Kommunikationsauthentifizierung nur auf dem API-Schlüssel.

Eine Lösung ist meiner Meinung nach, dass der JavaScript-Aufruf die Anforderung im Wesentlichen an eine interne Serverkomponente weiterleitet, die für einen Restaufruf verantwortlich ist. Angenommen, ein Servlet liest den API-Schlüssel aus einer gesicherten Quelle wie einem berechtigungsbasierten Dateisystem, fügt ihn in den HTTP-Header ein und führt den externen Restaufruf durch.

Ich hoffe das hilft.

MG Entwickler
quelle