# Sicherheit bei REST-Webservice



## DakineVester (18. Feb 2011)

Hallo zusammen,

ich habe einige Fragen bezüglich der Sicherheit bei REST-Webservies. Aber zunächst einmal ein paar Erklärungen zu dem geplaten Service.

Im Rahmen eines Projektes möchte ich einen Webservice erstellen, der zwei PDF-Dokumente verarbeitet und daraus ein drittes PDF-Dokument erstellt. Dabei habe ich mir es so vorgestellt, dass zuerst die Dokumente per FTP bzw. FTPS auf den entsprechenden Server geladen werden und nach erfolgreicher Übertragung der Webservice-Client aufgerufen werden soll. Dieser führt einen GET-Request aus und bekommt als Antwort den Pfad zum dritten Dokument. Danach soll halt ein FTPS-Download erfolgen.

Zuerst einmal eine Grundsätzliche Frage: Ist für diese Art von Service die REST-Methode tatsächlich die richtge?

Und nun zum eigentlichen Punkt:
Sicherheit spielt bei dem Projekt auch eine große Rolle. Deshalb die Frage, wie schützt man den Webservice am besten, und weitergehend, wie wird die Übertragung am besten verschlüsselt?

Nach meinen bisherigen Recherchen hat sich ergeben, dass die einfachste Methode, einen REST-Webservice zu sichern, die Http-Basis-Authentifizierung ist. Gibt es noch gute/bessere Alternativen? Für die verschlüsselte Übertragung ist nach meinem Kenntnisstand SSL eine gute Methode. Gibt es hierfür noch Alternativen?

Da ich noch Neuling bin im Bereich Webservice und auch im Bereich Java, ist mir jede konstruktive Kritik recht. Auch wenn die Vorgehensweise grundsätzlich unstimmig ist und beispielsweise der Einsatz von SOAP sinnvoller wäre.

Ich hoffe ihr könnt mir hilfreiche Tipps geben. 
Gruß, Hendrik


----------



## Noctarius (18. Feb 2011)

Ob das mit dem FTPS alles so gut ist bin ich mir nicht sicher, kommt aber auf den Verwendungszweck an. Ich würde eher per PUT ein neues "Enddokument" erstellen, per POST Mergefiles hinzufügen und per GET abholen. Dann könnte man auch sauber mit den REST-URIs arbeiten.

Sicherheit würde ist Stateless per Basic-Auth herstellen. Ergo meldet sich der Client quasi bei jedem Request über HTTP Header am Server an. Die Verbindung per HTTPS verschlüsselt und ein ausreichendes Sicherheitslevel sollte gegeben sein. Dies reicht zu mindestens für unsere sensiblen Bewerber- und Testdaten ;-)


----------



## DakineVester (18. Feb 2011)

Okay, die Bereitstellung der Dokumente werde ich nochmal überdenken. Nur um mich nochmal zu vergewissern, dass ich das richtig verstanden habe. Du würdest zunächst per POST die Dokumente dem Webservice bereitstellen, dann per PUT das Enddokument erstellen lassen, und dieses dann per GET "abholen"? 

Das Problem dabei ist, dass ich ja theoretisch per GET direkt das PDF-Dokument Anfragen kann, allerdings dabei keine Fehlermeldungen geliefert werden können (bis auf den HTTP-Status). Deswegen würde ich gerne als GET-Response wahrscheinlich Text nehmen. So kann ich dem Client sagen, dass z.b. Kriterien für die Erstellung des Enddokuments nicht richtig waren.

Eine weitere Frage ist die Zeitenoptimierung und die Auslastung der Kapazitäten des Servers. Wahrscheinlich wird der Webservice später mehrere Zugriffe gleichzeitig verarbeiten müssen. Aus dem Grund hatte ich gedacht, dass FTP sinnvoll ist und schneller ist als die HTTP POST Methode. Wenn das nicht der Fall ist, würde die POST-Methode sinnvoller bzw. einfacher sein.

Zur Sicherheit: Da bin ich bei meiner eigenen Recherche auch zu dem Entschluss gekommen, die Authentifizierung per Basic Authentication zu realisieren und die Datenübertragung per HTTPS abzusichern. Denke das sollte für das Projekt ausreichen.


----------



## Noctarius (18. Feb 2011)

PUT /service/document -> neue Document Id 123456
POST /service/document/123456 <- erstes Document schicken
POST /service/document/123456 <- zweites Document schicken
GET /service/document/123456 -> zusammengesetztes Document abholen, alternativ halt einen Fehler

Sollte das Document noch nicht zusammengebaut sein kannst du einen entsprechenden HTTP Code zurückliefern und definieren, dass der Client nach kurzer Wartezeit erneut anfragen soll, z.B. 412 (Precondition failed).


----------



## Gelöschtes Mitglied 5909 (18. Feb 2011)

&mdash; Project Kenai kann ich dir da Empfehlen.

Absichern kannst du das dann etwa so:


```
@Context 
SecurityContext securityContext

@POST
@Path("/document")
@Consumes({MediaType.APPLICATION_JSON})
public void sendDocument(Document document) {
  if (!securityContext.isUserInRole("Rolle") {
      throw new WebApplicationException(); // statuscode fobiddden setzten
  } 
  // verarbeiten
   
}
```


----------



## Noctarius (18. Feb 2011)

Falls man schon mit Spring gearbeitet hat würde ich die REST Unterstützung in Spring 3 empfehlen


----------



## DakineVester (21. Feb 2011)

Guten Morgen zusammen,

besten Dank schonmal für eure Beiträge! Ich werde mich heute mal damit beschäftigen, die Daten per POST dem Webservice zur Verarbeitung bereitzustellen. Für meine bisherigen Versuche habe ich mit dem Projekt Jersey gearbeitet, werde aber heute mich aber heute mal mit Spring beschäftigen. Wie gesagt, ich bin eigentlich Neueinsteiger was Java angeht.

Auch wie ich mit dem securityContext umgehen muss (wie ich z.B. die vorherige Authentifizierung durchführe. Ich vermute mal so auf Anhieb, dass es lediglich die Abfrage ist, ob die HTTPBasicAuth erfolgreich war.)

Mich wundert allerdings immer noch, dass die Übertragung der Dokumente per HTTP und nicht per FTP umgesetzt werden kann/soll. Ich hätte vermutet, dass eine FTP Übertragung schneller (bzw. ohne weniger Headerdaten) ist und da unabhängig vom eigentlichen Webservice, diesen ein wenig entlastet. Denn das wird ein wichtiger Punkt sein, da die Verarbeitung der Dokumente und die Erstellung des Enddokuments einiges an Rechenleistung in Anspruch nimmt. Auch die Zahl der Zugriffe wird vermutlich nicht gering bleiben.


----------



## Gelöschtes Mitglied 5909 (21. Feb 2011)

Basic Auth geht per web.xml (Mit Spring Security wirds auch gehn):

[xml]
  <!-- BASIC Authentication -->

  <login-config>
  	<auth-method>BASIC</auth-method>
  	<realm-name>Admin</realm-name>		<!-- hier den realm aus der server.xml angeben -->
  </login-config>

  <security-role>
  	<role-name>upload</role-name>
  </security-role>

  <security-constraint>
  	<web-resource-collection>
  		<web-resource-name>upload</web-resource-name>		<!-- Rest Servlet -->
  		<url-pattern>/*</url-pattern>										<!-- hier die geschützte url -->
  	</web-resource-collection>
  	<auth-constraint>
  		<role-name>upload</role-name>					
  		<!-- rolle die benötigt wird (kann auch Methodengenau mit dem SecurityContext überprüft werden -->
  	</auth-constraint>
  </security-constraint>
[/xml]


----------



## DakineVester (22. Feb 2011)

Ich denke ich bin gestern ein gutes Stück voran gekommen. Habe das Buch RESTful Java with JAX-RS von O'Reilly gelesen und meinen Webservice dann wie folgt aufgebaut:

POST -> erstes Dokument "hochladen"
POST -> zweites Dokument "hochladen"
PUT -> Enddokument erstellen
GET -> Enddokument herunterladen

So sollte das eher einem REST-Webservice entsprechen als mein vorheriger Ansatz.

Die Basic-Authentifizierung konnte ich auch soweit lösen, allerdings muss ich diese noch optimieren, da ich quasi bei jedem POST/PUT/GET eine neue Verbindung aufbaue und mich demnach neu authentifiziere.

Wo ich gerade dran sitze ist die Umsetzung ins HTTPS. Wird zwar auch im oben genannten Buch erwähnt und mit einem kurzen Beispiel erklärt, aber das verstehe ich noch nicht so ganz und das Implementieren des Beispiels in meinen Webservice klappt auch noch nicht so. Anscheinend gibt es Probleme mit "DerInputStream.getLength" beim Einlesen des Keys. Google sagt mir falsch Class eingebunden. Mal sehen wie ich das genau lösen kann.

Ab hier sollte ich aber alleine zu recht finden. Besten Dank für eure Hilfe! Meine Fragen bzgl. der Sicherheit wurden beantwortet und der Verbesserungsvorschlag zum allgemeinen Vorgehen war auch sehr hilfreich!


----------



## Noctarius (22. Feb 2011)

Ich würde die hochgeladenen Documents feste einen Enddokument zuordnen. Was machst du denn wenn hinterher 20 Leute gleichzeitig ihre Docs einstellen?

PUT /service/document -> neue Document Id 123456
PUT /service/document/123456/document/1 <- erstes Document schicken
PUT /service/document/123456/document/2 <- zweites Document schicken
GET /service/document/123456 -> zusammengesetztes Document abholen, alternativ halt einen Fehler

So z.B. wäre es auch gut, dann kannst du gleich noch eine Reihenfolge vorgeben aber in beliebiger Hochladen (z.B. paralleler Upload).


----------



## DakineVester (22. Feb 2011)

Eigentlich sollte das Hochladen von 20 Dokuemten gleichzeititg kein Problem sein, da die Dateien jeweils in einem Benutzerspezifischen Ordner abgelegt werden und der PUT-Befehl ebenfalls dem Webservice sagt, welche Dateien er "anpacken" soll, bzw. aus welchen er das Enddokument erstellen soll.

Quasi:

POST ../document/save/usrname/timestamp/doc1/blabla.pdf
POST ../document/save/usrname/timestamp/doc2/blubbl.pdf
>> Es liegen dann 2 Ordner mit jeweils einem Dokument im Verzeichnis "$usrname/$timestamp".

PUT ../document/create/usrname/timestamp
>> Erstellt dann aus den beiden Dokumenten das Enddokument

GET ../document/usrname/timestamp/
>> liefert dann das fertige Dokument


Hat jmd. vielleicht einen Link oder so für HTTPS? Ich schaffe es HTTPS Serverseitig zu konfigurieren. Per Browser kann ich den Webservice dann auch ganz normal aufrufen und er liefert mir das gewünschte Ergebnis. Das Problem liegt aber beim Java-Client. Dort bekomme ich halt den Fehler: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big.

Das ist meine Methode

```
public static SSLSocketFactory getFactory( File pKeyFile, String pKeyPassword ) throws Exception {
     KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
     KeyStore keyStore = KeyStore.getInstance("PKCS12");

     InputStream keyInput = new FileInputStream(pKeyFile);
     keyStore.load(keyInput, pKeyPassword.toCharArray());
     keyInput.close();

     keyManagerFactory.init(keyStore, pKeyPassword.toCharArray());

     SSLContext context = SSLContext.getInstance("TLS");
     context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());

     return context.getSocketFactory();
   }
```

Und so ist der Aufruf in der main:

```
URL url = new URL("https://localhost:8443/ws/document/save/" + type + "/" + filename);
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(getFactory(new File("cacerts.jks"), "changeit"));
        connection.setDoOutput(true);
        ...
```


----------



## Noctarius (22. Feb 2011)

Damit verlierst du aber den "Stateless" Effekt wenn du den Usernamen als State nutzt.

Für HTTPS würde ich einen Apache dem Tomcat vorschalten. Damit ist die Geschwindigkeit meiner Erfahrung nach besser, da der SSL Stack im Apache besser optimiert ist.


----------



## DakineVester (22. Feb 2011)

Ich verstehe den Unterschied nicht. In deinem Beispiel hast du eine ID, die eine Zuordnung zu den Dokumenten ermöglicht. In meinen Beispiel ist die ID quasi durch den Benutzer und die timestamp-Zahl ersetzt. Ist der Knackpunkt, dass man den Benutzernamen "speichert" bzw. verwendet? Im anderen Fall ist es ja die ID die man speichert/verwendet.  

Dein Ansatz ist aber rein technisch nicht möglich bzw. nicht sinnvoll. Ich muss zuerst die beiden Dokumente bereitstellen, da hieraus eine Differenz erstellt wird, und die 3 Dokumente (2 Ausgangsdokumente + 1 Enddokument) werden dann in einem PDF zusammengeführt.

Puh, den Apache vor den Tomcat schalten ist für mich wieder was ganz neues. Da muss ich mich dann auch erstmal schlau drüber machen wie das geht usw. Aber das sollte ja trotzdem nichts daran ändern, dass der Client nicht funktioniert. Die Funktionsweise Clientseitig ist doch wahrscheinlich ähnlich/gleich.


----------



## Noctarius (22. Feb 2011)

Der entscheidende Punkt ist, dass REST URIs als Document Identifiers nutzt. Wenn du den Usernamen nimmst brichst du dieses Konzept


----------



## DakineVester (22. Feb 2011)

So, ich habe das ganze nochmal überdacht und bin zu dem Entschluss gekommen, dass ich die Informationen des Benutzernamens gar nicht brauche. Gehe demnach wie folgt vor:


ID erzeugen mit timestamp
POST /services/document/{id} >> erstes Dokument hochladen
POST /services/document/{id} >> zweites Dokument hochladen
PUT /services/document/{id} >> Enddokument erstellen
GET /services/document/{id} >> Enddokument herunterladen

Ich denke das sollte auch dem REST-Konzept entsprechen. 

Zum HTTPS-Problem werde ich morgen noch einen neuen Thread eröffnen, da es ja dann eher auf den Code usw. eingeht und nicht auf die allgemeine Theorie.

Falls jmd noch weitere Vorschläge/Kritik hat, immer her damit. Ansonsten schonmal besten Dank für das bisherige Feedback und so!


----------



## Noctarius (22. Feb 2011)

Als ID würde ich eine UUID nutzen, diese brauchst du dann nicht mal vorher anmelden, sondern einfach mit der ID hochladen.


----------



## DakineVester (24. Mrz 2011)

Ich nochmal 

Ich hab mittlerweile den Webservice soweit fertig und es funktioniert auch alles. Allerdings kamen jetzt nochmal fragen zur Sicherheit auf, da laut meinem Kollegen die Authentifizierung mit HTTP Basic Access nicht ausreichend ist. Sein Vorschlag war "WS-Security".

Mein Problem ist jetzt aber, dass ich zwar recht viel zum Thema WS-Security finde, aber das alles hauptsächlich grobe Informationen sind. Und im Zusammenhang mit REST-Webservices finde ich quasi nix. 

Kann mir jemand da eventuell gute Links geben oder was dazu schreiben? Ist es möglich und sinnvoll die Authentifizierung des REST-Webservices über WS-Security zu lösen?

Besten Dank schonmal!


----------



## fax (24. Mrz 2011)

WS-Security ist ausschließlich für SOAP entworfen. Das würde ich nicht für REST verwenden. WS-Security baut auf XML Security auf. Letzteres wäre evtl. eine Option, wenn ihr mit REST XML Dokumente umherschiebt. Zunächst musst du mal klären, ob du auf verbindungsorientierte oder nachrichtenorientierte Sicherheit setzen willst.

P.S.: Wenn's nur um Authentifizierung geht, die populärsten Methoden sind SSL mit Benutzername/Passwort oder X.509 Zertifikaten oder alternativ OAuth. Letztendlich ist es REST egal, wie du das absicherst, das sind alles Mechanismen, die der Container mehr oder weniger transparent außerhalb von REST implementiert.


----------



## DakineVester (24. Mrz 2011)

Okay, dann fällt WS-Security also weg, da der Webservice quasi nix mit XML-Dokumenten am Hut hat.

Damit du vllt genauer verstehst, was ich absichern will, eben eine kleine Erklärung:
Es werden zwei PDF-Dokumente an den Webservice geschickt. Der WS verarbeitet die Dokumente dann und liefert auf GET-Anfrage das Ergebnis. Eine verschlüsselte Datenübertragung habe ich mit HTTPS gelöst. Die Authentifizierung ist dazu notwendig, den Webservice vor unbefugter Nutzung zu schützen. Einwand meines Kollegen war: Sobald einer die Session "abhört" kann er ja quasi weitere Anfragen an den WS schicken. 

Mein Einwand wäre: Dadurch, dass die Nachrichten ja verschlüsselt sind und man somit den Key bräuchte.

Ich werd mich jetzt nochmal ein wenig mit OAuth beschäftigen ... Besten Dank schonmal!


----------



## fax (25. Mrz 2011)

DakineVester hat gesagt.:


> Damit du vllt genauer verstehst, was ich absichern will, eben eine kleine Erklärung:
> Es werden zwei PDF-Dokumente an den Webservice geschickt. Der WS verarbeitet die Dokumente dann und liefert auf GET-Anfrage das Ergebnis. Eine verschlüsselte Datenübertragung habe ich mit HTTPS gelöst. Die Authentifizierung ist dazu notwendig, den Webservice vor unbefugter Nutzung zu schützen. Einwand meines Kollegen war: Sobald einer die Session "abhört" kann er ja quasi weitere Anfragen an den WS schicken.


Wie will jemand die Sitzung abhören, wenn sie mit SSL verschlüsselt ist? Gegen Replay Attacken ist SSL abgesichert, sofern man es nicht zerkonfiguriert hat.



DakineVester hat gesagt.:


> Ich werd mich jetzt nochmal ein wenig mit OAuth beschäftigen ... Besten Dank schonmal!


OAuth verwendet man eigentlich nur, wenn man die Credentials eines externen Webdienstes verwenden will, z.B. ein Facebook Benutzer kann sich bei TripIt mit dem Facebook Konto authentifizieren. Im Unternehmensnetz kann man das genauso gut in LDAP oder AD packen und den Webserver davor setzen.


----------

