So erstellen Sie einen sicheren WebSocket-Server in Python
Kingsley Ubah
5. Juli 2022
0 Min. LesezeitWenn eine Web-App Daten von einem externen Server benötigt, sendet der Client normalerweise eine Anfrage an den Server. Dieser antwortet, und anschließend wird die Verbindung geschlossen.
Stellen Sie sich eine Web-App vor, die Aktienkurse anzeigt. Damit die aktuellen Kurse angezeigt werden, muss der Client wiederholt aktualisierte Werte vom Server anfordern. Dieser Ansatz ist ineffizient, da die HTTP-Verbindung ständig getrennt und neu aufgebaut wird. Außerdem ist er alles andere als ideal: Der in der Datenbank gespeicherte Echtzeitkurs einer Aktie kann sich ändern, während die App noch den alten Kurs anzeigt.
Bei einer bidirektionalen Kommunikation kann der Server neue Aktienkurse jedoch jedes Mal eigenständig an den Client senden, wenn die Daten aktualisiert werden. Das WebSocket-Protokoll wurde entwickelt, um diese Art persistenter Verbindung zu ermöglichen und zugleich die Probleme älterer Long-Polling-Verfahren zu vermeiden.
Eine beliebte Option für die Implementierung von WebSockets ist die Bibliothek Socket.IO. Einer ihrer wichtigsten Vorteile ist, dass sie in verschiedenen Programmierumgebungen eingesetzt werden kann, darunter Python. Außerdem bietet sie in beiden Umgebungen eine einfachere und einheitlichere API, sodass Sie die nativen WebSocket-APIs nicht direkt verwenden müssen. Darüber hinaus erleichtert die Bibliothek die Implementierung von Sicherheitsmaßnahmen wie Cross-Origin Resource Sharing (CORS), Benutzerauthentifizierung und Beschränkungen der Payload-Größe.
In diesem Tutorial erfahren Sie, wie Sie mit python-socket.io und dem JavaScript-Socket.IO-Client einen sicheren WebSocket-Server in Python erstellen und den Client mit dem Server verbinden.
Einen WebSocket-Server mit Python erstellen
Installation und Einrichtung
Zu Beginn benötigen Sie Python 3.x und den dazugehörigen Paketmanager pip auf Ihrem Rechner. Glücklicherweise ist pip in allen aktuellen Python-Versionen enthalten. Falls Python noch nicht installiert ist, können Sie die neueste Version für Ihr System herunterladen.
Erstellen Sie nun die Quelldateien. Legen Sie zunächst einen Ordner für den Beispielcode an. Installieren Sie anschließend mit pip das Paket python-socketio:
Erstellen Sie dann im Ordner quick-socket eine neue Datei namens app.py. Hier fügen Sie den Code für Ihren Socket-Server ein.
Einen WebSocket-Server in Python erstellen
Importieren Sie zunächst die Socket.IO-Bibliothek für Python und erstellen Sie in app.py eine asynchrone Socket-Serverinstanz. Legen Sie dafür den Wert von async_mode auf asgi fest, damit ein ASGI-Server entsteht. Richten Sie anschließend Event-Listener für die Instanz ein:
Das erste Event wird ausgelöst, wenn eine Socket-Verbindung zu unserem Server hergestellt wird. Die zugehörige Funktion connect akzeptiert zwei Argumente:
sidsteht fürsession id, eine eindeutige ID, die einen verbundenen Client kennzeichnet.socketist ein Dictionary mit allen Informationen zum Client. Übergeben Sie es anconnect(), um bei der Authentifizierung des Clients identifizierende Informationen (zum Beispiel einen Benutzernamen) zu prüfen.
Das letzte Event wird ausgelöst, wenn in der Client-App eine Schaltfläche angeklickt wird. Die Payload-Nachricht (hero) dient dazu, den zugehörigen Namen aus dem am Anfang der Datei definierten Dictionary abzurufen.
Sie benötigen einen Webserver, der die serverseitige Socket-Anwendung für den Client bereitstellt. Dafür verwenden wir ASGI. Die python-socket.io-Bibliothek enthält praktischerweise ASGIApp. Damit können wir app.py in eine ASGI-Anwendung umwandeln, die sich mit einem WSGI-Server wie Gunicorn verbinden lässt.
Erstellen Sie eine Instanz dieser Klasse in der Zeile unter server_io = socketio.AsyncServer(). Übergeben Sie dabei die Socket-Serverinstanz und den Pfad zu den zuvor erstellten statischen Dateien:
Zum Schluss müssen Sie die Anwendung bereitstellen. Für Socket.IO-Server gibt es verschiedene Bereitstellungsstrategien, zum Beispiel mit Gunicorn und Eventlet. Lesen Sie die Dokumentation und wählen Sie die Strategie, die am besten zu Ihren Anforderungen passt.
Einen WebSocket-Client mit Socket.IO erstellen
Nachdem Sie nun einen WebSocket-Server erstellt haben, können Sie einen WebSocket-Client einrichten, der mit ihm kommuniziert.
Erstellen Sie zunächst im Stammordner einen Ordner namens client. Legen Sie darin zwei Dateien an: index.html und index.js.
Fügen Sie in index.html einfaches HTML-Markup ein und binden Sie Links zum Socket.IO-Client, zum Bootstrap-CSS und zu Ihrer lokalen Skriptdatei index.js ein:

In index.js verarbeiten Sie alle vom Server ausgelösten Events. Außerdem lösen Sie ein Event aus, wenn eine beliebige Schaltfläche auf der Seite angeklickt wird:
Der obige Code ruft das Attribut id der angeklickten Schaltfläche ab und sendet dessen Wert an den Server. Daraufhin löst der Server das Event name aus. Im abschließenden Callback, der beim Ausführen des Events name aufgerufen wird, zeigen wir den Namen mit der JavaScript-Funktion alert an.
Rufen Sie in Ihrem Browser 127.0.0.1:8000/index.html auf. Auf der Seite sollte eine Liste mit Schaltflächen angezeigt werden, eine für jede Heldin und jeden Helden.
Klicken Sie auf eine beliebige Schaltfläche, um das Event get_name auszulösen. Daraufhin wird der Name der Heldin oder des Helden in einem Hinweisfenster angezeigt.

Sehen wir uns nun die WebSocket-Sicherheit an.
Ihre WebSockets absichern
Um Ihre WebSockets abzusichern, müssen Sie bestimmte Funktionen auf dem Server implementieren.
Einige dieser Funktionen sind in python-socketio standardmäßig aktiviert, wie auch in vielen anderen WebSocket-Serverbibliotheken. Die Bibliothek komprimiert beispielsweise HTTP-Daten in WebSocket-Verbindungen automatisch. Das verbessert die Übertragungsgeschwindigkeit und senkt den Bandbreitenverbrauch.
Andere Funktionen müssen Sie bei der Initialisierung des WebSocket-Servers implementieren oder aktivieren.
Mit CORS Domains zulassen oder blockieren
Cross-Origin Resource Sharing (CORS) ermöglicht Web-Clients, Cross-Origin-Anfragen zu stellen. CORS-Einschränkungen schützen den Server vor Cross-Site-Request-Forgery-Angriffen (CSRF). Dabei bringt ein Angreifer das Opfer meist dazu, unbeabsichtigte und oft schädliche Aktionen auszuführen, etwa Geld zu überweisen.
Der Schutz durch CORS gilt jedoch nur für HTTP-Verbindungen. CORS-Richtlinien greifen bei WebSockets nicht, da WebSocket-Verbindungen das WebSocket-Protokoll (WS) oder das WebSocket-Secure-Protokoll (WSS) verwenden. Bei diesen Protokollen erfolgt der erste Handshake über eine HTTP-Upgrade-Anfrage. Der Antworttext wird ignoriert, und das HTTP-/HTTPS-Protokoll wird auf WS/WSS umgestellt.
Da CORS den Zugriff auf WebSocket-Protokolle nicht einschränkt, können böswillige Nutzer problemlos Cross-Origin-WebSocket-Verbindungen herstellen, um schädliche Daten zu senden und zu empfangen. Die Lösung besteht darin, das Protokoll auf WS umzustellen, indem Sie der Client-Anfrage das Header-Feld upgrade hinzufügen.
Als Antwort sendet der Server die Meldung 101 switching protocols. Damit bestätigt er, dass die weitere Kommunikation über WebSocket erfolgen kann. Die Socket.IO-Clientbibliothek übernimmt diesen Vorgang automatisch. Weitere Konfigurationsoptionen finden Sie hier.
Der Server überprüft den Header Origin in der Upgrade-Anfrage, um unerwünschte Cross-Origin-WS-Verbindungen zu verhindern. Die python-socket.io-Bibliothek bietet dafür den Parameter cors_allowed_origins. Beim Initialisieren des serverseitigen WebSockets kann dieser Parameter einen einzelnen Ursprung oder eine Liste von Ursprüngen (ein Array mit URLs) annehmen.
Sie können dieses Argument auch auf '*' setzen, um alle Origins zuzulassen, oder auf [], um alle Origins zu blockieren:
Um Angriffe durch Cross-Site WebSocket Hijacking (CSWSH) zu verhindern, können Sie außerdem Verbindungen von anderen Ursprüngen als Ihrem eigenen blockieren. Diese Angriffsart ist eine Variante von CSRF-Angriffen und ermöglicht Lese- und Schreibzugriffe über WebSockets.
Angreifer können eine bösartige Webseite auf ihrer Domain erstellen und damit eine Verbindung zum Server-Socket herstellen, wobei sie sich als der Nutzer ausgeben. Die bösartige Anwendung kann dann Nachrichten lesen, die der Server sendet, und direkt Nachrichten an den Server schreiben.
Beachten Sie, dass manche Nicht-Browser-Clients den Origin-Header problemlos setzen können. Ergänzen Sie diesen Ansatz daher durch weitere Formen der Client-Authentifizierung.
WebSocket-Clients vor dem Verbinden authentifizieren
Mit dem Wachstum und der zunehmenden Komplexität des Webs entwickeln sich auch Cyberangriffe weiter. Ein Authentifizierungssystem ist eine der wirksamsten Methoden, Datendiebstahl zu verhindern und die Privatsphäre bei der Client-Server-Kommunikation zu schützen.
Ein Benutzerauthentifizierungssystem bildet eine Barriere zwischen Client und Server. Wer auf Ressourcen des Servers zugreifen möchte, muss zunächst im Anmeldeformular eindeutige Identifikationsdaten angeben – normalerweise einen Benutzernamen und ein Passwort.
Der Server überprüft die Angaben und genehmigt oder verweigert damit die Anmeldeanfrage. Lehnt der Server die Anfrage ab, zeigt er dem Nutzer den Grund dafür an, zum Beispiel: „Ihre Anmeldedaten sind falsch.“ So lässt sich verhindern, dass Ressourcen in die falschen Hände geraten.
Nutzer können ein starkes Passwort erstellen und einen Passwortmanager verwenden, um die Sicherheit der Authentifizierung zu verbessern. Diese Maßnahmen hängen jedoch vollständig vom Nutzer ab. Das ist ein weiterer wichtiger Grund, robuste Authentifizierungssysteme serverseitig in der Anwendung zu implementieren.
Für die serverseitige Authentifizierung stehen zahlreiche Bibliotheken zur Verfügung. Welche Sie wählen, hängt davon ab, welches serverseitige Framework Sie für die Anwendung verwenden.
Für unser WebSocket-Beispiel können Sie im Ordner public eine Datei namens login.html erstellen, die ein HTML-Formular im Browser anzeigt. Darin können Nutzer ihren Benutzernamen und ihr Passwort eingeben.
Warten Sie dann im Client-Skript public/index.js auf ein submit-Event. Wenn der Nutzer das Anmeldeformular absendet, initialisieren Sie die WebSocket-Verbindung zwischen Client und Server und senden den angegebenen Benutzernamen und das Passwort als extraHeaders:
Die obige Konfiguration ruft anhand ihrer id-Attribute die Werte für Benutzername und Passwort aus den Eingabefeldern ab, kodiert sie als HTTP-Header und überträgt sie, sobald der Client eine Verbindung herstellt, an den WebSocket-Server.
Um den Nutzer im Servercode app.py zu authentifizieren, rufen wir Benutzername und Passwort aus request ab:
Hier übernehmen wir Benutzername und Passwort und prüfen, ob Authentifizierungsdaten vorliegen. Sind keine Anmeldedaten vorhanden, hat der Nutzer keine gesendet. In diesem Fall geben wir False zurück und können den Nutzer nicht authentifizieren.
Andernfalls fahren wir mit der Authentifizierung fort. Normalerweise prüfen Sie, ob der Benutzername in einer Datenbank vorhanden ist, und rufen damit nutzerspezifische Inhalte ab. Anschließend erstellen Sie eine Benutzersitzung und senden die Nachricht user_authorized an den Client. Mit JavaScript im Clientcode können Sie diese Inhalte dann dem Nutzer anzeigen.
Ohne WebSocket-Authentifizierung kann sich potenziell jeder mit dem Server verbinden und sensible Daten stehlen. Sobald ein Nutzer authentifiziert und angemeldet ist, sollten Sie für die weitere Authentifizierung Tokens verwenden, statt erneut nach Benutzername und Passwort zu fragen.
Seien Sie außerdem vorsichtig mit Daten, die vom Client gesendet werden. Validieren Sie alle Client-Eingaben, bevor Sie sie verarbeiten. Angriffe wie SQL-Injection sind über WebSockets ebenso möglich wie über herkömmliche HTTP-Verbindungen.
Ihren WebSocket-Server mit Rate Limiting vor Angriffen schützen
Rate Limiting schützt vor Denial-of-Service-Angriffen (DoS) und verteilten Denial-of-Service-Angriffen (DDoS). Dabei versucht ein Angreifer, einen Dienst durch zahlreiche wiederholte Anfragen zu überlasten oder sogar zum Absturz zu bringen, sodass er für legitime Nutzer nicht mehr verfügbar ist.
Rate-Limiting verhindert diese Angriffe, indem es die Häufigkeit der API-Anfragen begrenzt, die ein Nutzer stellen kann. Diese Begrenzung lässt sich anhand von Schlüsseln wie einer IP-Adresse, einem API-Schlüssel oder einem anderen eindeutigen Bezeichner wie UniqueId durchsetzen.
Rate- und Kontingentlimits, auch Quotas genannt, legen fest, wie viele Anfragen ein Client innerhalb eines bestimmten Zeitraums an eine API stellen kann. Dienstanbieter setzen diese Methode in der Regel ein, um eine faire Nutzung API-basierter Dienste und Ressourcen sicherzustellen.
Um Rate-Limiting auf Ihrem Server durchzusetzen, müssen Sie zunächst wissen, warum es erforderlich ist – etwa zum Schutz eines Dienstes oder zum Festlegen von Kontingenten für verschiedene Tarife. Als Nächstes müssen Sie den für Ihren Anwendungsfall am besten geeigneten Schlüssel zur Begrenzung ermitteln und auswählen. Anschließend verwenden Sie eine entsprechende Implementierung, um die API-Nutzung anhand des gewählten Schlüssels zu verfolgen.
Zwar gibt es derzeit kein speziell für python-socketio entwickeltes Rate-Limiting-Paket, aber es gibt eine Lösung, die Sie mit etwas Aufwand implementieren können.
Begrenzen Sie die Payload-Größe, um Ihren WebSocket-Server zu schützen
Eine sehr große Payload über WebSockets zu senden, beeinträchtigt mit hoher Wahrscheinlichkeit die Performance – und bringt letztlich den Socket-Server zum Absturz.
Um eine Verlangsamung oder einen Absturz des WebSockets zu vermeiden, sollten Sie wahrscheinlich die maximale Payload-Größe für Nachrichten begrenzen, die über die WebSocket-Verbindung gesendet werden. So verhindern Sie, dass eine übergroße Nachricht (z. B. eine Zip-Bombe) den Server zum Absturz bringt.
Außerdem reduziert die Begrenzung der Nachrichtengröße die Latenz der WebSocket-Verbindung erheblich und kann die Übertragungsgeschwindigkeit deutlich verbessern. Das WebSocket-Protokoll begrenzt die Größe eines Frames und hilft dabei, den erforderlichen Komprimierungsgrad der Nachricht zu bestimmen. Die Komprimierung des Nachrichten-Streams kann Speicher- und CPU-Ressourcen beanspruchen, lohnt sich aber oft, da sie den Netzwerkverkehr erheblich reduzieren kann.
Verwenden Sie TLS für eine sichere Socket-Kommunikation
Die Verwendung von TLS/SSL ist unverzichtbar, wenn Sie eine Website im Internet bereitstellen. Ohne diese Verschlüsselung können vertrauliche Informationen, die zwischen Client und Server übertragen werden, leicht durch einen Man-in-the-Middle-Angriff gestohlen werden. Verwenden Sie für eine sichere Übertragung ein sicheres Protokoll wie https:// statt des unsicheren Protokolls http://.
Für WebSockets sollten Sie das Protokoll wss:// (TLS-verschlüsselte WebSockets) anstelle des unsicheren Protokolls ws:// verwenden. Bei der zweiten Variante ist die Verbindung anfällig für Eingriffe Dritter. Im Gegensatz dazu verschlüsselt das Protokoll wss:// alle über den WebSocket gesendeten Daten.
Dank dieser Verschlüsselung können Dritte die über den WebSocket gesendeten Nachrichten weder lesen noch verändern. So werden vertrauliche Informationen geschützt. Auch andere Angriffe sind bei einer sicheren Verbindung nicht möglich.
Stellen Sie außerdem sicher, dass die anfragende Website https:// verwendet, bevor Sie eine WebSocket-Verbindung zum Server herstellen – andernfalls können Angreifer die Anfragen leicht manipulieren.
TLS ist für jede Website notwendig, unabhängig davon, ob sie WebSockets verwendet. Glücklicherweise stellen viele Hosting-Anbieter bei der Bereitstellung von Anwendungen TLS kostenlos zur Verfügung. Zu den beliebtesten Hosting-Plattformen für Python-Apps gehören Google Cloud, AWS, Azure, Heroku und Fly.io.
Fazit zu WebSockets
In diesem Tutorial haben wir mit python-socketio einen einfachen WebSocket-Server erstellt und mit einem JavaScript-Client verbunden. Außerdem haben wir gelernt, wie WebSockets funktionieren, und einige Sicherheitsaspekte besprochen, damit Sie sie sicher verwenden können.
Wie viele andere Webprotokolle sind auch WebSockets auf ähnliche Maßnahmen (TLS, CORS, Rate-Limiting usw.) angewiesen, um die Kommunikation angemessen zu schützen. Keine dieser Maßnahmen ist jedoch spezifisch für WebSockets. Sie zu verstehen ist dennoch entscheidend, um unnötige Sicherheitsprobleme zu vermeiden.
Kostenloses Online-Tool zur Codeprüfung
Sichern Sie Ihren Code, bevor Sie Ihren nächsten Commit erstellen.



