OAuth erlaubt es Zugriffe auf private Daten welche in einer Website gespeicher sind von jeder Anwendung aus zu gestatten ohne dazu gezwungen zu sein den Benutzernamen oder das Passwort herauszugeben. Wenn man darüber nachdenk ist die Praxis der Herausgabe von Benutzername und Passwort für Sites wie Yahoo Mail oder Twitter seit einer ganzen Weile endemisch. Dies hat einige Bedenken ausgelöst weil es nichts gibt um zu verhindern das Anwendungen diese Daten missbrauchen. Ja, einige Serives mögen vertrauenswürdig erscheinen aber dies kann nie garantiert werden. OAuth löst dieses Problem indem es die Notwendigkeit eliminiert Benutzernamen und Passwörter zu teilen, und es mit einem vom Benutzer kontrollierten Authorisationsprozess ersetzt.
Dieser Authorisationsprozess basiert auf Tokens. Wenn man eine Anwendung authorisiert (wobei eine Anwendung jede Webbasierende- oder Desktop- anwendung enthält) auf die eigenen Daten zuzugreifen, wird diese einen Access Token erhalten der mit dem eigenen Account assoziiert ist. Bei Verwendung dieses Access Tokens kann die Anwendungen auf die privaten Daten zugreifen ohne dauernd die Zugangsdaten zu benötigen. Insgesamt ist dieses Prokoll einer delegationsartigen Authorisierung eine sicherere Lösung des Problems auf private Daten über eine beliebige Webservice API zuzugreifen.
OAuth ist keine komplett neue Idee, es ist mehr ein standardisiertes Protokoll welches auf existierende Eigenschaften von Protokollen wie Google AuthSub, Yahoo BBAuth, Flickr API, usw. aufsetzt. Alle von Ihnen arbeiten im weiteren Sinne auf der Basis einer standardisierten Benutzerkennung für eine Art Access Token. Der Vorteil einer standardisierten Spezifikation wie OAuth ist, das Sie nur eine einzelne Implementation benötigt im gegensatz zu vielen unterschiedlichen abhängig vom verwendeten Webservice. Diese Standardisierung hat nicht unabhängig von den Major Players stattgefunden, und aktuell unterstützen viele bereits OAuth als Alternative und wollen in Zukunft Ihre eigenen Lösungen damit ersetzen.
Zend Framework's Zend_Oauth
implementiert aktuell über die Klasse
Zend_Oauth_Consumer
einen vollständigen OAuth Konsumenten welcher der
OAuth Core 1.0 Revision A Spezifikation (24 Juni 2009) entspricht.
Bevor OAuth implementiert wird macht es Sinn zu verstehen wie das Protokoll arbeitet. Hierfür nehmen wir ein Beispiel von Twitter welches aktuell bereits OAuth basierend auf der OAuth Core 1.0 Revision A Spezifikation imeplementiert. Dieses Beispiel sieht das Protokoll aus der Perspektive des Benutzers (der den Zugriff gestattet), dem Konsumenten (der den Zugriff anfragt) und dem Provider (der die privaten Daten des Benutzers hat). Ein Zugriff kann nur-lesend, oder lesend und schreibend sein.
Durch Zufall hat unser Benutzer entschieden das er einen neuen Service verwenden will der TweetExpress genannt wird und behauptet in der Lage zu sein Blog Posts bei Twitter in wenigen Sekunden nochmals zu posten. TweetExpress ist bei Twitter eine registrierte Anwendung was bedeutet das es Zugriff auf einen Konsumentenschlüssel und ein Geheimnis des Konsumenten hat (alle OAuth Anwendungen müssen diese vom Provider haben auf welchen Sie zugreifen wollen) welche seine Anfragen bei Twitter identifizieren und sicherstellen das alle Anfragen signiert werden können indem das Geheimnis des Konsumenten verwendet wird um deren Herkunft zu prüfen.
Um TweetExpress zu verwenden wird man danach gefragt sich für einen neuen Account zu registrieren, und das der Registrierung wird sichergestellt das man dafüber informiert ist das TweetExpress den eigenen Twitter Account mit dem Service assoziiert.
In der Zwischenzeit was TweenExpress beschäftigt. Bevor das Einverständnis von Twitter gegeben wird, hat es eine HTTP Anfrage an den Service von Twitter geschickt und nach einem neuen unauthorisierten Anfrage Token gefragt. Dieser Token ist aus der Perspektive von Twitter nicht Benutzerspezifisch, aber TweetExpress man Ihn spezifisch für den aktuellen Benutzer verwenden und sollte Ihn mit seinem Account verknüpfen und Ihn für eine künftige Verwendung speichern. TweetExpress leitet den Benutzer nun zu Twitter um damit er den Zugriff von TweetExpress erlauben kann. Die URL für diese Umleitung wird signiert indem das Konsumentengeheimnis von TweetExpress verwendet wird und Sie enthält den unauthorisierten Anfrage Token als Parameter.
An diesem Punkt kann der Benutzer gefragt werden sich in Twitter anzumelden und wird jetzt mit einem Twitter Bildschirm konfrontiert welcher Ihn fragt ob er diese Anfrage von TweetExpress für den Zugriff auf die API von Twitter im Auftrag des Benutzers gestattet. Twitter speichert die Antwort von der wir annehmen das Sie positiv war. Basierend auf dem Einverständnis des Benutzers speichert Twitter den aktuell unauthorisierten Anfrage Token als vom Benutzer akzeptiert (was Ihn Benutzerspezifisch macht) und erzeugt einen neuen Wert in der Form eines Überprüfungscodes. Der Benutzer wird jetzt auf eine spezifische Callback URL zurückgeleitet welche von TweetExpress verwendet wird (diese Callback URL kann bei Twitter registriert sein, oder dynamisch gesetzt werden indem bei den Anfragen ein oauth_callback Parameter verwendet wird). Die Umleitungs-URL wird den neu erzeugten Überprüfungscode enthalten.
Die Callback URL von TweetExpress löst eine Überprüfung der Anfrage aus um zu erkennen ob der Benutzer seine Zustimmung an Twitter gegeben hat. Wir nehmen an das dies der Fall war, dann kann jetzt sein nicht authorisierter Anfrage Token gegen einen voll authorisierten Anfrage Token getauscht werden indem eine Anfrage an Twitter zurückgesendet wird inklusive dem Anfrage Token und dem empfangenen Überprüfungscode. Twitter sollte jetzt eine Antwort zurücksenden welche diesen Zugriffstoken enthält welcher in allen Anfragen verwendet werden muss um Zugriff auf die API von Twitter im Auftrag des Benutzers zu erhalten. Twitter macht das nur einmal sobald bestätigt wurde das der angehängte Anfrage Token noch nicht verwendet wurde um einen anderen Anfrage Token zu erhalten. Ab diesem Punkt kann TweetExpress dem Benutzer die Anfrage der Akzeptanz bestätigen und den originalen Anfrage Token löschen da er nicht länger benötigt wird.
Ab diesem Punkt kann TweetExpress die API von Twitter verwenden um neue Tweets im Sinne des Benutzers zu schicken indem einfach auf die Endpunkte der API mit einer Anfrage zugegriffen wird welche digital signiert wurde (über HMAC-SHA1) mit einer Kombination von dem Konsumenten Geheimnis von TweetExpress und dem Zugriffsschlüssel der verwendet wird.
Auch wenn Twitter den Zugriffstoken nicht ablaufen lässt, steht es dem Benutzer frei TweetExpress zu de-authorisieren damit es nicht mehr auf seine Einstellungen des Twitter Accounts zugreifen kann. Sobald er de-authorisiert wurde, wird der Zugriff von TweetExpress abgeschnitten und sein eigener Zugriffstoken wird als ungültig dargestellt.
OAuth wurde speziell designt um über eine unsichere HTTP Verbindung zu
arbeiten und deshalb ist die Verwendung von HTTPS nicht notwendig obwohl
es natürlich wünschenswert wäre wenn es vorhanden ist. Sollte eine HTTPS
Verbindung möglich sein bietet OAuth die Implementation einer Signaturmethode an welche
PLAINTEXT heißt und verwendet werden kann. Über eine typische unsichere
HTTP Verbindung muss die Verwendung von PLAINTEXT verhindert werden und
ein alternatives Schema wird verwendet. Die OAuth Spezifikation definiert zwei solcher
Signaturmethoden: HMAC-SHA1 und RSA-SHA1. Beide werden von Zend_Oauth
vollständig unterstützt.
Diese Signaturmethoden sind recht einfach zu verstehen. Wie man sich vorstellen kann macht die PLAINTEXT Signaturmethode nichts das erwähnenswert wäre da Sie auf HTTPS aufsetzt. Wenn man aber PLAINTEXT über HTTP verwenden würde, dann würde ein signifikantes Problem bestehen: Es gibt keinen Weg um sicherzustellen das der Inhalt einer OAuth-aktivierten Anfrage (welche einen OAuth Zugriffstoken enthalten würde) beim Routen verändert wurde. Das ist der Fall weil unsichere HTTP Anfragen immer das Risiko des Lauschens, von Man In The Middle (MITM) Attacken, oder andere Risiken haben können in denen eine Anfrage weiterbearbeitet werden könnten und damit Arbeiten im Sinne des Angreifers ausgeführt werden indem sich dieser als Originalanwendung maskiert ohne das dies vom Serviceprovider bemerkt wird.
HMAC-SHA1 und RSA-SHA1 vermindern dieses Risiko indem alle OAuth Anfragen mit dem originalen von der Anwendung registrierten Konsumentengeheimnis digital signiert werden. Angenommen nur der Konsument und der Provider wissen was das Geheimnis ist, dann kann ein Mann in der Mitte soviele Anfragen verändern wie er will - aber er wird nicht in der Lage sein sie gültig zu signieren und unsignierte oder ungültig signierte Anfragen würden von beiden Parteien ausgeschieden werden. Digitale Signaturen bieten deshalb eine Garantie das gültig signierte Anfragen von der erwarteten Partei kommen und beim Routen nicht verändert wurden. Das ist der Kern warum OAuth über eine unsichere Verbindung arbeiten kann.
Wie diese digitalen Signaturen arbeiten hängt von der verwendeten Methode ab, z.B. HMAC-SHA1, RSA-SHA1 oder möglicherweise eine andere Methode welche vom Serviceprovider definiert wird. HMAC-SHA1 ist ein einfacher Mechanismus welcher einen Nachrichten Authentifizierungscode (MAC) erzeugt indem eine kryptographische Hash Funktion (z.B. SHA1) in Verbindung mit einem geheimen Schlüssel verwendet wird der nur dem Sender und dem Empfänger der Nachricht bekannt sind (z.b. das Konsumentengeheimnis von OAuth kombiniert mit dem authorisierten Zugriffsschlüssel). Dieser Hashing Mechanismus wird den Parametern und dem Inhalt aller OAuth Anfragen angehängt und zu einem "Basissignatur String" zusammengefügt wie es von der OAuth Spezifikation definiert ist.
RSA-SHA1 operiert auf ähnlichen Prinzipien ausser dass das Geheimnis welches geteilt wird, wie man es erwarten würde, der private RSA Schlüssel jeder Partei ist. Beide Seiten haben den öffentlichen Schlüssel des anderen mit dem die digitalen Signaturen geprüft werden. Das führt verglichen mit HMAC-SHA1 zu einem Riskolevel da die RSA Methode keinen Zugriffsschlüssel als teil des geteilten Geheimnisses verwendet. Dies bedeutet dass wenn der private RSA Schlüssel eines Konsumenten kompromitiert ist, sind es alle zugeordneten Zugriffstoken dieses Konsumenten auch. RSA führt zu einem alles oder gar nichts Schema. Generell tendiert die Mehrheit der Serviceprovider welche OAuth Authorisierung anbieten dazu HMAC-SHA1 standardmäßig zu verwenden, und jene welche RSA-SHA1 anbieten können eine Fallback Unterstützung für HMAC-SHA1 anbieten.
Wärend digitale Signaturen zur Sicherheit von OAuth beitragen sind Sie trotzdem für andere Formen von Attacken angreifbar, wie Replay Attacken welche vorhergehende Anfragen aufgezeichnet und zu einer Zeit geprüft und signiert wurden. Ein Angreifen können jetzt exakt die gleiche Anfrage zum Provider wie er will und zu jeder Zeit senden und seine Ergebnisse auffangen. Das führt zu einem signifikanten Risiko aber es ist recht einfach sich davor zu schützen - einen eindeutigen String (z.b. eine Nonce) bei allen Anfragen hinzufügen welcher sich bei jeder Anfrage ändert (dies verändert laufend den Signaturstring) kann aber niemals wiederverwendet werden weil Provider verwendete Nonces zusammen mit einem bestimmten Fenster aktiv verfolgen welches vom Timestamp definiert wird der einer Anfrage auch angehängt wird. Man würde erwarten das wenn die Verfolgung einer bestimmten Nonce gestoppt wird, das wiederabspielen funktionieren würde, aber das ignoriert den Timestamp der verwendet werden kann um das Alter einer Anfrage zu ermitteln zu welcher Sie digital signiert wurde. Man kann also annehmen dass die eine Woche alte Anfrage in einem Wiederholungsversuch abgespielt wird, auf ähnliche Weise verworfen wird.
Als letzter Punkt erwähnt, ist dies keine exzessive Ansicht der Sicherheitsarchitektur in OAuth. Was passiert zum Beispiel wenn HTTP Anfragen welche sowohl den Zugriffstoken als auch das Geheimnis des Konsumenten enthalten abgehört werden? Das System ist auf der einen Seite von einer klaren Übermittlung von allem abhängig solange HTTPS nicht aktiv ist. Deshalb ist die naheliegende Feststellung das HTTPS, dort wo es möglich ist zu bevorzugen ist und HTTP nur an solchen Orten eingesetzt wird so es nicht anders möglich oder nicht erschwinglich ist.
Da das OAuth Protokoll jetzt erklärt wurde sehen wir uns ein einfaches Beispiel mit Quellcode an. Unser neuer Konsument wird Twitter Statusübertragungen verwenden. Um das tun zu können muss er bei Twitter registriert sein um einen OAuth Konsumentenschlüssel und ein Konsumentengeheimnis zu empfangen. Diese werden verwendet um einen Zugriffstoken zu erhalten bevor wir die Twitter API verwenden um eine Statusmeldung zu schicken.
Angenommen wir haben einen Schlüssel und ein Geheimnis bekommen, dann können wir den OAuth
Workflow starten indem eine Zend_Oauth_Consumer
Instanz wie folgt
eingerichtet und eine Konfiguration übergeben wird (entweder ein Array oder ein
Zend_Config
Objekt).
$config = array( 'callbackUrl' => 'http://example.com/callback.php', 'siteUrl' => 'http://twitter.com/oauth', 'consumerKey' => 'gg3DsFTW9OU9eWPnbuPzQ', 'consumerSecret' => 'tFB0fyWLSMf74lkEu9FTyoHXcazOWpbrAjTCCK48A' ); $consumer = new Zend_Oauth_Consumer($config);
callbackUrl ist die URI von der wir wollen das Sie Twitter von unserem Server anfragt wenn
Informationen gesendet werden. Wir sehen uns das später an. siteUrl ist die Basis URL der
OAuth API Endpunkte von Twitter. Die komplette Liste der Endpunkt enthält
http://twitter.com/oauth/request_token, http://twitter.com/oauth/access_token und
http://twitter.com/oauth/authorize. Die grundsätzliche siteUrl verwendet eine Konvention
welche auf diese drei OAuth Endpunkte verweist (als Standard) um einen Anfragetoken, den
Zugriffstoken oder die Authorisierung zu erhalten. Wenn sich der aktuelle Endpunkt eines
beliebigen Services vom Standardset unterscheidet, dann können diese drei URIs separat
gesetzt werden indem die Methoden setRequestTokenUrl()
,
setAccessTokenUrl()
, und
setAuthorizeUrl()
, oder die Konfigurationsfelder requestTokenUrl,
accessTokenUrl und authorizeUrl verwendet werden.
consumerKey und consumerSecret werden von Twitter empfangen wenn sich die eigene Anwendung für den OAuth Zugriff registriert. Das wird auch bei jedem OAuth aktivierten Service so angewendet, so dass jeder einen Schlüssel und ein Geheimnis für die eigene Anwendung anbietet.
Alle diese Konfigurationsoptionen müssen durch Verwendung von Methodenaufrufen gesetzt werden indem Sie einfach von callbackUrl auf setCallbackUrl() konvertiert werden.
Zusätzlich sollte beachtet werden das verschiedene andere Konfigurationswerte nicht explizit
verwendet werden: requestMethod und requestScheme. Standardmäßig sendet
Zend_Oauth_Consumer
Anfragen als POST (ausgenommen bei einer
Weiterleitung welche GET
verwendet). Der konsumierende Client (siehe
später) enthält auch seine Authorisierung in Art eines Headers. Einige Services können, zu
Ihrer Diskretion, Alternativen benötigen. Man kann requestMethod (welche standardmäßig
Zend_Oauth::POST ist) zum Beispiel auf Zend_Oauth::GET zurückgesetzt, und requestScheme von
seinem Standardwert Zend_Oauth::REQUEST_SCHEME_HEADER entweder auf
Zend_Oauth::REQUEST_SCHEME_POSTBODY oder auf Zend_Oauth::REQUEST_SCHEME_QUERYSTRING.
Typischerweise sollten die Standardwerte bis auf ein paar bestimmte Ausnahmen problemlos
funktionieren. Für Details sehen Sie bitte in die Dokumentation des Service Providers.
Der zweite Teil der Anpassung definiert wie HMAC arbeitet wenn es für
alle Anfragen berechnet und verglichen wird. Das wird durch Verwendung des
Konfigurationsfeldes signatureMethod oder durch
setSignatureMethod()
konfiguriert. Standardmäßig ist es HMAC-SHA1.
Man kann es auch auf die vom Provider bevorzugte Methode setzen inklusive RSA-SHA1. Für
RSA-SHA1 sollte man die privaten und öffentlichen RSA Schlüssel über die
Konfigurationsfelder rsaPrivateKey und rsaPublicKey oder die Methoden
setRsaPrivateKey()
und setRsaPublicKey()
konfigurieren.
Der erste Teil des OAuth Workflows holt sich einen Anfragetoken. Das wird bewerkstelligt indem folgendes verwendet wird:
$config = array( 'callbackUrl' => 'http://example.com/callback.php', 'siteUrl' => 'http://twitter.com/oauth', 'consumerKey' => 'gg3DsFTW9OU9eWPnbuPzQ', 'consumerSecret' => 'tFB0fyWLSMf74lkEu9FTyoHXcazOWpbrAjTCCK48A' ); $consumer = new Zend_Oauth_Consumer($config); // Holt den Anfragetoken $token = $consumer->getRequestToken();
Der neue Anfragetoken (eine Instanz von Zend_Oauth_Token_Request
)
ist nicht authorisiert. Um Ihn mit einem authorisierten Token zu wechseln mit dem wir auf
die Twitter API zugreifen können, muss Ihn der Benutzer authorisieren.
Wir bewerkstelligen das indem der Benutzer auf den Authorisierungsendpunkt von Twitter
umgeleitet wird:
$config = array( 'callbackUrl' => 'http://example.com/callback.php', 'siteUrl' => 'http://twitter.com/oauth', 'consumerKey' => 'gg3DsFTW9OU9eWPnbuPzQ', 'consumerSecret' => 'tFB0fyWLSMf74lkEu9FTyoHXcazOWpbrAjTCCK48A' ); $consumer = new Zend_Oauth_Consumer($config); // Holt den Anfragetoken $token = $consumer->getRequestToken(); // Den token im Speicher fixieren $_SESSION['TWITTER_REQUEST_TOKEN'] = serialize($token); // Den Benutzer umleiten $consumer->redirect();
Der Benutzer wird jetzt auf Twitter umgeleitet. Er wird gefragt den Anfragetoken zu authorisieren, welcher an den Anfragestring der umgeleiteten URI angehängt ist. Angenommen er akzeptiert und vervollständigt die Authorisierung, dann wird er wieder umgeleitet. Dieses Mal auf unsere Callback URL die vorher gesetzt wurde (Beachte das die Callback URL auch in Twitter registriert wurde als wir unsere Anwendung registriert haben).
Bevor der Benutzer umgeleitet wird, sollten wir den Anfragetoken im Speicher fixieren. Der Einfachheit halber verwenden wir nur die Session des Benutzer, aber man kann sehr einfach eine Datenbank für den gleichen Zweck verwenden, solange der Anfragetoken mit dem aktuellen Benutzer verbunden bleibt, damit er empfangen werden kann wenn dieser zu unserer Anwendung zurückkommt.
Die umgeleitete URI von Twitter enthält einen authorisierten Zugriffstoken. Wir können Code einbauen um diesen Zugriffstoken wie folgt herauszuschneiden - dieser Sourcecode würde im ausgeführten Code unserer Callback URI existieren. Sobald er herausgeschnitten wurde können wir den vorherigen Anfragetoken entfernen, und statt dessen den Zugriffstoken für die zukünftige Verendung mit der API von Twitter fixieren. Nochmals, wir fixieren einfach die Session des Benutzer, aber in Wirklichkeit kann ein Zugriffstoken eine lange Lebenszeit haben, und sollte deshalb wirklich in einer Datenbank abgespeichert werden.
$config = array( 'callbackUrl' => 'http://example.com/callback.php', 'siteUrl' => 'http://twitter.com/oauth', 'consumerKey' => 'gg3DsFTW9OU9eWPnbuPzQ', 'consumerSecret' => 'tFB0fyWLSMf74lkEu9FTyoHXcazOWpbrAjTCCK48A' ); $consumer = new Zend_Oauth_Consumer($config); if (!empty($_GET) && isset($_SESSION['TWITTER_REQUEST_TOKEN'])) { $token = $consumer->getAccessToken( $_GET, unserialize($_SESSION['TWITTER_REQUEST_TOKEN']) ); $_SESSION['TWITTER_ACCESS_TOKEN'] = serialize($token); // Jetzt da wir den Zugriffstoken haben können wir den Anfragetoken löschen $_SESSION['TWITTER_REQUEST_TOKEN'] = null; } else { // Fehlgeschlagene Anfrage? Ein Gauner versucht etwas? exit('Ungültige Callback Anfrage. Oops. Entschuldigung.'); }
Erfolg! Wir haben einen authorisierten Zugriffstoken - zu dieser Zeit verwenden wir schon
die API von Twitter. Da dieser Zugriffstoken bei jeder einzelnen
API Anfrage enthalten sein muss, bietet
Zend_Oauth_Consumer
einen fix-fertigen HTTP Client
an (eine Subklasse von Zend_Http_Client
) welcher entweder für sich
verwendet werden, oder der als eigener HTTP Client an eine andere
Bibliothek oder Komponente übergeben werden kann. Hier ist ein Beispiel für die
eigenständige Verwendung. Das kann von überall aus der Anwendung heraus getan werden,
solange man Zugriff auf die OAuth Konfiguration hat, und den endgültigen authorisierten
Zugriffstoken empfangen kann.
$config = array( 'callbackUrl' => 'http://example.com/callback.php', 'siteUrl' => 'http://twitter.com/oauth', 'consumerKey' => 'gg3DsFTW9OU9eWPnbuPzQ', 'consumerSecret' => 'tFB0fyWLSMf74lkEu9FTyoHXcazOWpbrAjTCCK48A' ); $statusMessage = 'Ich sende über Twitter und verwende Zend_Oauth!'; $token = unserialize($_SESSION['TWITTER_ACCESS_TOKEN']); $client = $token->getHttpClient($configuration); $client->setUri('http://twitter.com/statuses/update.json'); $client->setMethod(Zend_Http_Client::POST); $client->setParameterPost('status', $statusMessage); $response = $client->request(); $data = Zend_Json::decode($response->getBody()); $result = $response->getBody(); if (isset($data->text)) { $result = 'true'; } echo $result;
Als Notiz zum eigenen Client, kann dieser an den meisten Services von Zend Framework
übergeben werden, oder an andere Klassen welche Zend_Http_Client
verwenden um damit den Standardclient zu ersetzen welcher andernfalls verwendet werden
würde.