"Mein Name ist Mr. Wolf, ich löse Probleme." (PulpFiction)

Donnerstag, 27. Juni 2013

Die Same Origin Policy (SOP) beim Zugriff auf Resourcen von anderen Servern

Wie im Artikel über den JavaScript WebService versprochen, hier die Anleitung zum Zugriff auf entfernte WebServices aus JavaScript.
Was ist eigentlich das Problem?
In einem JavaScript ist es möglich eine Verbindung zu einem Service zu erstellen, und über diese Informationen auszutauschen.

var req = new XMLHttpRequest();
req.open('POST',"http://myOwnPage.com/get_time/",false)
req.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?><soap-env:Envelope xmlns:soap-env...</soap-env:Envelope>");
alert(req.responseText);
Leider funktioniert die Verbindung nur in der selben  Domain. Die selbe Domain bedeutet nicht nur die selbe Adresse, sondern auch den selben Port, also:
Kommt man von http://myPage.com:8080/ dann funktioniert zwar die Verbindung zu http://myPage.com:8080/myWebService aber weder http://notMyPage.com/notMyWebService noch http://myPage.com:8081/myWebService werden erreichbar sein.
Da nun aber der JBOSS auf dem entwickelt wird lokal auf meinem Rechner liegt, und der WebService auf einem Rechner im Netz, muss eine Lösung her.
Was ich versucht habe (nur der Vollständigkeit halber)
  • Einfache Port Weiterleitung von meinem Rechner auf den WebService Rechner. Da wusste ich noch nicht, dass der Port zur Domain gehört, und bin natürlich kläglich gescheitert.
  • Eine Rewriting Rule für den JBOSS geschrieben, um dem Consumer vorzugaukeln, er würde den WebService lokal erreichen. Ebenfalls gescheitert. Die Rule greift zwar für die reine Verbindung, aber bereits der req.send() Befehl scheitert sie.
Also hab ich mal wieder nach was offiziellem gesucht, und wurde fündig:

Es gibt zwei Lösungen, bei beiden braucht man Zugriff auf den Server zu dem man sich verbinden möchte.
  • Persistent JSON (PJSON), mehr ein Workaround in meinen Augen. Kann nur über HTTP GET  benutzt werden
  • Cross-Origin Resource Sharing ist das andere Zauberwort. Der "Access-Control-Allow-Origin" Header ist leicht zu implementieren, jedoch nicht ganz ausgereift, und nur auf sehr aktuellen Browsern verfügbar.
PJSON (Persistant JSON)
PJSON nutzt die Tatsache aus, dass man ein JavaScript praktisch von jedem beliebigen Server einbinden kann. Wenn wir also auf myPage.com ein JavaScript von notMyPage.com brauchen, dann ist das recht trivial:
<script src ="http://notMyPage.com/src/public_script.js"></script>
und wir können die Methoden benutzen. Das Schöne ist, das Skript wird auch gleich ausgeführt, d.h. wenn auf der Seite eine Funktion gestartet werden würde, würde diese auch gleich lokal ausgeführt.
Dies nutzt die PJSON Methode aus, es wird quasi ein JavaScript auf notMyPage.com dynamisch generiert. Sendet ein Client eine Anfrage in der Form:
<script src ="http://notMyPage.com/src/public_script.js?func=get_local_time&city=Berlin&callback=alert_time"></script>

könnte auf notMyPage.com ein Script gestartet werden, welches die WebService Methode get_local_time mit dem Parameter "Berlin" aufruft. Das Resultat würde z.B. als Variablenzuweisung in die Rückgabe geschrieben, und am Ende die Funktion alert_time() aufgerufen.
Da myPage.com das generierte Script liest und direkt ausführt, können so recht komplexe Objekte transferiert werden. Durch die Ausführung der Callback Funktion ist das ganze sogar asynchron möglich. Jedoch ist der Aufruf über die GET Methode sehr suboptimal. Komplexe Strukturen werden da schnell hässlich und unübersichtlich.
Außerdem, seinen wir mal ehrlich, ich frickel ja auch gern, aber das klingt nach Arbeit. Das Generieren des JavaScripts muss ja implementiert werden. Dann hat der Zugriff ja wieder herzlichst wenig mit einem WebService zu tun, das was wir eigentlich im Sinn hatten. Gibt's da nicht was schöneres? Ja, gibt es, was sehr viel schöneres:
Das Cross-Origin Resource Sharing
 Dieser Ansatz funktioniert etwas anders: Schickt der Server nämlich den so genannten "Access-Control-Allow-Origin" Header mit einem IP Adressbereich in dem sich der Client befindet, so weiß dieser, "Ich darf da rein". Also wird die Verbindung anstandslos aufgebaut, und kann benutzt werden. Hier zeigt sich auch sofort, dass die Kontrolle ganz am Client hängt. Es sind aber keinerlei Anpassungen auf der Client Seite nötig, die Abfrage erledigt der Browser für uns.
Bei dieser Lösung bin ich allerdings auf ein Problem gestoßen. Wenn der Client mit dem Request zum Webservice auch eigene Header sendet, funktioniert das ganze im Firefox (Version 21.0) nicht mehr. Aus dem generierten wsdl2js JavaScript Client musste ich also die Header raus nehmen, damit das ganze klappt.
Wie man im JBOSS eigene Header sendet, oder eine rewrite Rule anlegt, gibt's dann ein nächstes mal. Das ist mir einen eigenen Post wert.

Kommentare:

  1. Das sind in meinen Augen allerdings alles Workarounds. Was spricht gegen die Lösung, einen Ajax-Request an den eigenen Server zu schicken, der dann eine Weiterleitung an den eigentlich gewünschten Endpunkt durchführt?

    AntwortenLöschen
    Antworten
    1. Hallo, danke für deinen Kommentar, und noch einen Workaround! Ich habe einen WebService, also ein Standard Interface, und einen Client, also einen Verbraucher der genau auf dieses Interface abgestimmt ist. Warum soll da jetzt noch was dazwischen? Sei es PJSON, oder AJAX. Alle anderen Clients, sei es Java, C oder sonstwas würden auch direkt drauf zugreifen.
      Die Header Methode ist in meinen Augen deshalb kein Workaround, weil hier ein direkter Zugriff, der auch standardisiert ist benutzt wird. Wenn ich es darf, also der WS es erlaubt, möchte ich auch direkt mit ihm kommunizieren.

      Löschen