Mit Hilfe von WebID und Linked Data ist es möglich das Web zu einem großen sozialen Netzwerk auszubauen. Möglicherweise habt ihr meinen Vortrag über WebID gesehen und fragt euch nun, wie ihr ein WebID-Login in eure Webanwendung einbauen könnt? Gute Nachricht: Es ist total einfach – und ich zeige euch jetzt wie es geht!
Wir werden:
- Den Dienst von foafssl.org nutzen um eine WebID zu verifizieren
- Die Antwort von foafssl.org auswerten
- Den WebID-User mit einem Benutzer unserer Anwendung verknüpfen
[important]Ich benutze im Beispielcode das Grails-Webframework, aber das grundsätzliche Vorgehen ist mit jedem anderen Framwork gleich.[/important]
Verifikation durch foafssl.org
Prinzipiell funktioniert ein WebID-Login ganz ohne Abhängigkeit zu irgendwelchen Anbietern. Für den Anfang ist es aber am einfachsten, einen bestehenden Dienst zur Verifikation einer WebID zu nutzen, anstatt alles selbst zu machen.
Um einem WebID-User die Authentifizierung mit seinem Zertifikat zu ermöglichen, müssen wir ihn lediglich zu einer bestimmten URL des Dienstes weiterleiten und zwar zu
https://foafssl.org/srv/idp?rs=<Antwort-URL>
Wobei die <Antwort-URL> eine bestimmte URL unserer Webanwendung ist, welche die Antwort von foafssl.org entgegennehmen und auswerten wird.
Im Grails-Webframework sind das gerade einmal zwei Zeilen Code:
class WebIdController { def login () { def rs = createLink (action: 'verify', absolute: true).encodeAsURL () redirect (url: "https://foafssl.org/srv/idp?rs=${rs}") } }
In Zeile 3 generieren wir einen absoluten Link auf die verify-Action des WebIdControllers und encodieren ihn, sodass er als URL-Parameter geeignet ist. In Zeile 4 erfolgt die Weiterleitung zu foafssl.org inklusive unserer verify-URL als Parameter.
Antwort auswerten
In der verify-Action werten wir anschließend die Antwort von foafssl.org aus. Werfen wir einen Blick auf die Antwort-Parameter:
webid: Die WebID des authentifizierten Users ts: Ein Zeitstempel, wann die Authentifizierung durchgeführt wurde sig: Die Signatur des Dienstes
Die Signatur ist besonders wichtig, da wir prüfen müssen, ob die Antwort wirklich von foafssl.org stammt und nicht manipuliert wurde. Widmen wir uns also zunächst der Überprüfung dieser Signatur. Stellt sie sich als ungültig heraus müssen wir die Antwort verwerfen und dürfen den Nutzer nicht einloggen.
Signatur prüfen
Die Signatur ist nichts anderes als unsere verify-URL inklusive der Parameter „webid“ und „ts“ signiert mit dem RSA-Schlüssel von foafssl.org. Der öffentliche Schlüssel der zur Überprüfung der Signatur notwendig ist, wurde in der Dokumentation des Dienstes veröffentlicht. Wir müssen also nichts weiter tun als mit einer Bibliothek unserer Wahl und diesem Public-Key die Signatur zu prüfen. Ich habe wie folgt die Java-Standardbibliothek verwendet:
BigInteger MODULUS = new BigInteger("9093ac0285..." , 16); BigInteger PUBLIC_EXPONENT = new BigInteger("65537", 10); def publicKeySpec = new RSAPublicKeySpec(MODULUS, PUBLIC_EXPONENT); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); byte[] data = "$verifyUrl?webid=${URLEncoder.encode(webId)}&ts=${URLEncoder.encode(timestamp)}".getBytes() Signature sig = Signature.getInstance("SHA1withRSA"); sig.initVerify(publicKey); sig.update(data); return sig.verify(Base64.decodeBase64(signatureBase64));
In den Zeilen 1-3 erzeugen wir einen PublicKey aus den Daten (MODULUS, PUBLIC_EXPONENT) die uns in der Dokumentation mitgeteilt wurden. In Zeile 4 bauen wir aus unserer verfiy-URL, der übermittelten webId und dem übermittelten timestamp, eben jene URL, die foafssl.org signiert haben sollte. Die Parameter müssen dazu URL-encodiert sein. Weil Grails URL-Parameter automatisch decodiert, muss ich sie hier wieder encodieren. Das mag aber bei eurem Framework ggf. anders sein.
Anschließend übergeben wir in den Zeilen 6 und 7 sowohl den Key, als auch die angeblich signierten Daten in ein Signature-Objekt und gleichen die Signatur in Zeile 8 mit der übermittelten ab. Zu beachten ist, dass die übermittelte Signatur Base64-URL(!)1-Encodiert ist und wir sie zunächst decodieren müssen.
Timestamp prüfen
Neben der Signatur müssen wir auch den Zeitstempel überprüfen um Replay-Attacken zu erschweren. Nachdem wir die Echtheit durch Überprüfung der Signatur sicher gestellt haben, prüfen wir nun mittels Timestamp, dass die Beglaubigung von foafssl.org nicht „zu alt“ ist. Was als „zu alt“ gilt ist eine etwas knifflige Gratwanderung. Einerseits dürfen wir den Zeitraum nicht zu kurz wählen, sonst werden womöglich gültige Requests die etwas länger dauern, als veraltet verworfen. Wählen wir den Zeitraum zu kurz, kann man (oder ein Hacker, der die Kommunikation abgehört hat!) sich später mit Hilfe des gleichen Requests einfach wieder einloggen, ohne sich tatsächlich über WebID zu authentifizieren. Ich halte eine Gültigkeitsdauer von 5 Minuten für einen angemessenen Kompromiss, bin aber für Diskussionen offen2.
Wir müssen also nun lediglich den Timestamp parsen und mit der aktuellen Uhrzeit abgleichen:
Date verifiedDate = new SimpleDateFormat(TIMESTAMP_FORMAT).parse(timestamp) def differenceInMillis = new Date().getTime() - verifiedDate.getTime() return (differenceInMillis / 1000) <= TIMESTAMP_LIFESPAN
Ein Zeitstempel von foafssl.org sieht konkret z.B. so aus:
2012-09-04T12-22-28-0700
Dies entspricht bei Java/Groovy folgendem Datumsformat:
yyyy-MM-dd'T'HH:mm:ssZ
Mit Überprüfung der Signatur und des Zeitstempels haben wir das Gröbste hinter uns! Fehlt nur noch das eigentliche „Einloggen“ unseres Nutzers.
WebID-Nutzer mit lokalem User-Account verbinden
Wir haben sichergestellt, dass die Antwort von foafssl.org korrekt und aktuell ist und können nun dem WebID-Nutzer Zugriff auf unsere Anwendung gewähren. Was genau das bedeutet, hängt letztendlich von eurer Anwendung ab. In meinem aktuellen Project picserv lege ich für WebID-Nutzer einen neuen Account an, oder lade einen bereits existierenden, der mit dieser WebID verknüpft ist:
session.user = webIdAccountService.loadOrCreateUserAccount(webId) flash.message = "You successfully logged in with your WebID $webId" redirect (controller: 'picture')
Ggf. habt ihr aber auch bereits einen User in der Session und wollt diesen lediglich mit seiner nun bestätigten WebID verbinden:
session.user.webId = webId flash.message = "You connected your account with your WebID $webId" redirect (controller: 'myAccount')
Da es sich bei einer WebID um eine URI handelt, die bei Abruf Linked Data bereitstellt, könnt ihr über die WebID natürlich auch weitere Informationen über euren Nutzer laden! 3
Das wars!
Geschafft! Mit wenigen Zeilen Code haben wir eine Web-Anwendung um ein Login mittels WebID erweitert und damit den ersten Schritt gemacht, unsere Anwendung dem Social Web of Data zu öffnen. Wer Gefallen an der Idee des dezentralen Social Webs gefunden hat, kann nun beginnen, die Daten seiner Anwendung als Linked Data zu veröffentlichen und mit den WebIDs der Nutzer zu verlinken.
[important]Verständnisfragen? Probleme? Lob? Kritik? Schreib einen Kommentar oder sende mir eine E-Mail[/important]
- Im Gegensatz zur gewöhnlichen Base64-Encodierung werden hierbei die Zeichen + und / durch – und _ ersetzt. ↩
- 5 Minuten mögen für ein Request sehr lang erscheinen, wir müssen aber neben der reinen Übertragungsdauer auch berücksichtigen (und tolerieren), dass die Zeiten der Server um einige Minuten voneinander abweichen können. ↩
- groovyrdf wird das Laden und Lesen von Daten in einem kommenden Release unterstützen. ↩