Einzelne Best Practices für Webleistung und Sicherheit sind ausführlich dokumentiert, jedoch selten werden zusammen als zusammenhängendes System erklärt: TLS überall, Sitzungscookies richtig konfiguriert, kein JWT in localStorage, aggressives Caching nur dort, wo es sicher ist, Echter Widerruf von Sitzungen und WebSockets, Ratenbegrenzung auf Echtzeitkanälen und Überwachung der Verbindung all diese Signale. Dieser Leitfaden behandelt zehn Praktiken, die zusammen angewendet die Symptome tatsächlich reduzieren Angriffsfläche ohne Einbußen bei der von den Benutzern wahrgenommenen Leistung.
Jeder Abschnitt enthält konkrete Konfigurationen (Nginx, Express, NestJS, WebSocket) und die tatsächlichen Kompromisse jeder Wahl – denn jede Praxis hat ihren Preis, und ihre Anwendung ohne Verständnis führt zu Konfigurationen Cargo-Kult, der eigentlich nichts schützt.
1. HTTPS/WSS erforderlich
TLS ist weder für HTTP noch für WebSocket verhandelbar: ohne wss://, ein Klartext-WebSocket
stellt Sitzungstokens und Anwendungsnutzlasten allen Personen im selben Netzwerk zur Verfügung. Konfigurieren Sie TLS 1.2 als
Minimum (1,3, wo möglich), HSTS mit Vorlast und OCSP-Stapling, um Latenz zu vermeiden
eine Echtzeit-Zertifikatsüberprüfung bei jedem Handshake.
# nginx.conf — TLS 1.2/1.3, HSTS, OCSP stapling
server {
listen 443 ssl;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
location /ws/ {
proxy_pass http://backend_ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
2. Sitzungscookies: HttpOnly, Secure, SameSite
HttpOnly verhindert, dass JavaScript das Cookie liest (verhindert Diebstahl über XSS),
Sicher sendet es nur über HTTPS, SameSite steuert standortübergreifendes Senden:
Strikt für maximalen CSRF-Schutz (unterbricht jedoch Flüsse mit externen Weiterleitungen),
Lax als vernünftiger Kompromiss für die meisten Anwendungen,
Keine nur, wenn das Cookie wirklich standortübergreifend sein muss – und in diesem Fall ist es erforderlich
Sicher obligatorisch.
// Express/NestJS — cookie di sessione configurato correttamente
res.cookie('session_id', sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 15 * 60 * 1000, // 15 minuti, coerente con la strategia di refresh
});
3. Niemals JWT in localStorage
localStorage kann von jedem auf der Seite ausgeführten Skript gelesen werden: ein einzelnes XSS (even
in einer Abhängigkeit von einem Drittanbieter, nicht in Ihrem Code) exfiltriert das Token, ohne dass eine Interaktion erforderlich ist
Benutzer. Die sichere Alternative ist das HttpOnly-Cookie für das Token selbst, kombiniert mit
das Double-Submit-Cookie-Muster für den CSRF-Schutz, wenn Sie auch ein
zustandsloser Mechanismus.
// Refresh token in HttpOnly cookie, access token short-lived in memoria (mai in storage persistente)
app.post('/auth/refresh', (req, res) => {
const refreshToken = req.cookies.refresh_token; // HttpOnly, non leggibile da JS
const newAccessToken = issueAccessToken(verifyRefreshToken(refreshToken));
res.json({ accessToken: newAccessToken }); // vive solo in memoria lato client, mai in localStorage
});
4. Statischer Asset-Cache mit Fingerabdruck
Ein Asset mit einem Hash im Dateinamen (main.a1b2c3d4.js) kann ein ganzes Jahr lang zwischengespeichert werden
mit unveränderlich, da jede Änderung am Inhalt automatisch einen Dateinamen generiert
anders – das Ungültigmachen des Caches erfordert nie eine manuelle Löschung.
# Angular CLI genera automaticamente asset con hash nel filename in build di produzione
ng build --configuration production
# Output: main.a1b2c3d4.js, styles.e5f6a7b8.css
# Header cache per asset fingerprinted
location ~* \.[0-9a-f]{8}\.(js|css)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
5. Sensible Antworten nicht zwischenspeichern
Jede API-Antwort mit persönlichen oder Benutzersitzungsdaten muss explizit angegeben werden
Cache-Control: no-store (nie gespeichert, nicht einmal im temporären Speicher) oder private
(Caching nur über den Browser des Benutzers, niemals über einen gemeinsam genutzten Cache/CDN). Wenn die Antwort unterschiedlich ist
basierend auf einem Header wie Authorization oder Accept-Language, deklarieren Sie ihn mit
Variieren, um zu verhindern, dass ein Zwischencache die falsche Benutzerantwort bereitstellt.
// NestJS — risposta con dati personali, mai cacheabile
@Get('me')
getProfile(@Res({ passthrough: true }) res: Response) {
res.setHeader('Cache-Control', 'no-store');
return this.userService.getCurrentProfile();
}
6. CDN mit s-maxage Separate from Browser
s-maxage steuert, wie lange das CDN (gemeinsamer Cache) die Antwort speichert,
unabhängig von der Steuerung des Browsers des Benutzers – dies ermöglicht
Stellen Sie minutenlang quasi-statische Inhalte von der Edge bereit und halten Sie Ihren Browser in wenigen Minuten auf dem neuesten Stand
Sekunden oder umgekehrt.
# Header: CDN cachea 5 minuti, browser solo 30 secondi
Cache-Control: public, s-maxage=300, max-age=30
# Purge mirata di un singolo path sulla CDN dopo un deploy
curl -X POST "https://api.cdn-provider.com/purge" -H "Authorization: Bearer $CDN_TOKEN" -d '{"path":"/api/products"}'
7. Aktualisieren und widerrufen Sie Token für Sitzungen und WebSockets
Ein kurzlebiges Zugriffstoken (10–15 Minuten) sowie ein langlebigeres HttpOnly-Aktualisierungstoken begrenzen die Beschädigung des Fensters im Falle eines Diebstahls. Echter Widerruf erfordert einen serverseitigen Status (eine Blacklist oder ein Versionszähler pro Benutzer): Ein rein zustandsloses JWT kann nicht sein vor ihrem natürlichen Ablauf ungültig. Bei WebSockets muss der Widerruf aktiv geschlossen werden Um eine Verbindung herzustellen, lehnen Sie nachfolgende HTTP-Anfragen nicht einfach ab.
// Revoca sessione: invalida il refresh token e chiude ogni WebSocket associato all'utente
async function revokeSession(userId: string) {
await sessionStore.incrementTokenVersion(userId); // invalida tutti i JWT emessi prima di ora
for (const socket of wsConnections.getByUserId(userId)) {
socket.close(4001, 'session_revoked');
}
}
8. Ratenbegrenzung und Größenbeschränkung für WebSocket
Ohne Einschränkungen kann ein einzelner Client den Server mit Nachrichten oder Nutzlasten hoher Geschwindigkeit überlasten riesig – ist die einfachste Form von Anwendungs-DoS, die gegen einen Nicht-WebSocket-Kanal ausgeführt werden kann geschützt.
// ws — rate limiting e size limit per connessione
const wss = new WebSocketServer({ maxPayload: 64 * 1024 }); // 64KB max per messaggio
wss.on('connection', (socket) => {
let messageCount = 0;
const resetInterval = setInterval(() => (messageCount = 0), 1000);
socket.on('message', (data) => {
if (++messageCount > 20) return socket.close(4008, 'rate_limit_exceeded'); // max 20 msg/sec
handleMessage(data);
});
socket.on('close', () => clearInterval(resetInterval));
});
9. Überwachung
Die zu verfolgenden Metriken decken vier Bereiche ab: Verbindungen (aktive WebSockets, Rate Wiederverbindung), Latenz/Durchsatz (API-Antwortzeit, Nachrichten/Sekunde), Cache (Hit/Miss-Verhältnis für das CDN, effektive TTL beobachtet) und auth (Fehlerrate bei Aktualisierungstoken, widerrufene Sitzungen). Prometheus + Grafana decken Metriken gut ab Echtzeit-Zahlen und Dashboards; ELK (oder ein Äquivalent) eignet sich weiterhin am besten für die Anwendungsprotokollierung Strukturierte und Ad-hoc-Recherche zu Sicherheitsereignissen.
// Prometheus — contatore custom per connessioni WebSocket attive
const wsConnectionsGauge = new client.Gauge({ name: 'ws_active_connections', help: 'Connessioni WebSocket attive' });
wss.on('connection', () => {
wsConnectionsGauge.inc();
return () => wsConnectionsGauge.dec();
});
10. Zugänglichkeits- und Datenschutztest
Stellen Sie sicher, dass keine Cookies oder zwischengespeicherten Header versehentlich personenbezogene Daten preisgeben (a
Set-Cookie mit Klartext-E-Mail im Namen, eine öffentlich zwischengespeicherte Antwort, die
enthält den Benutzernamen). Automatisierte Tests (Axe-Core für a11y, IC-Sicherheits-Header-Scanner)
sie decken objektive Fälle ab; eine regelmäßige manuelle Überprüfung der Cache-Control-Header
Routen mit persönlichen Daten bleiben notwendig, da ein neuer Endpunkt leicht vergessen werden kann
der richtige Header.
Schnelle Betriebscheckliste
- Hohe Priorität: HTTPS/WSS überall, HttpOnly+Secure+SameSite-Sitzungscookie, alle JWT aus localStorage entfernen.
- Mittlere Priorität: statisches Asset-Fingerprinting,
No-StoreHeader bei Antworten mit persönlichen Daten, Ratenbegrenzung für WebSocket. - Kontinuierliche Priorität: Verbindungs-/Cache-/Authentifizierungsüberwachung, regelmäßige Überprüfung der Cache-Header auf neuen Routen.
30/60/90-Tage-Plan
Tage 1-30
- HTTPS/WSS überall erzwungen, HSTS aktiv – KPI: 0 Endpunkte im Klartext erreichbar.
- Sitzungscookies wurden nach HttpOnly+Secure+SameSite migriert – KPI: 0 Token aus clientseitigem JavaScript gelesen.
Tage 31-60
- Ratenbegrenzung aktiv auf allen WebSocket-Kanälen – KPI: 0 Verbindungen, die die festgelegten Grenzwerte überschreiten können.
- Caching-Header auf allen Routen mit persönlichen Daten korrigiert – KPI: Vollständige Prüfung, 0 öffentlich zwischenspeicherbare sensible Antworten.
Tage 61-90
- Komplettes Überwachungs-Dashboard (Verbindungen, Cache, Authentifizierung) – KPI: Alarme für jede kritische Metrik konfiguriert.
- Token- und WebSocket-Sperrung durchgängig getestet – KPI: effektive Sperrzeit unter 2 Sekunden.
FAQ
SameSite=Streng oder lax für ein Sitzungscookie?
Lax ist für die meisten Anwendungen der richtige Kompromiss; Nur streng, wenn Sie keine Flows mit Weiterleitungen von externen Domänen zur App haben.
Warum nicht einfach sessionStorage anstelle von localStorage für das JWT verwenden?
Beide sind für JavaScript gleichermaßen lesbar und daher anfällig für XSS: Der Unterschied besteht nur in der Haltbarkeit, nicht in der Sicherheit.
funktioniert-maxage ohne konfiguriertes CDN?
Nein, es wird von Browsern ignoriert: Es betrifft nur kompatible freigegebene Caches wie CDNs oder Reverse-Proxys, die es explizit unterstützen.
Wie widerrufe ich ein JWT, bevor es abläuft?
Mit serverseitigem Status (Blacklist oder benutzerspezifische Token-Version), da ein reines zustandsloses JWT nicht vor dem natürlichen Ablauf ungültig gemacht werden kann.
Was ist ein vernünftiger Grenzwert für Nachrichten pro Sekunde für einen WebSocket?
Hängt vom Anwendungsfall ab, aber 10–30 Nachrichten/Sekunde pro Verbindung sind ein vernünftiger Ausgangspunkt für die meisten Echtzeitanwendungen.
Ist OCSP-Heftung obligatorisch?
Nicht obligatorisch, aber dringend empfohlen: Reduziert die TLS-Handshake-Latenz, indem verhindert wird, dass der Client die Zertifizierungsstelle separat kontaktiert.
Wie verhindern Sie, dass ein CDN eine sensible Antwort an den falschen Kunden sendet?
Mit Cache-Kontrolle: privat oder keine Speicherung für persönliche Antworten, niemals öffentlich, wenn die Daten je nach Benutzer variieren.
Müssen Sie auch Token-Aktualisierungsfehler überwachen?
Ja, ein plötzlicher Anstieg von Aktualisierungsfehlern ist oft das erste Anzeichen eines laufenden Angriffs oder eines Token-Ablauffehlers.
Häufige Fehler, die es zu vermeiden gilt
- Klartext-WebSocket (
ws://) hinter einem HTTPS-Frontend: Der Browser lässt dies nur zu, wenn sich der WebSocket auf demselben unsicheren Ursprung befindet, aber dennoch Klartextdaten dem Netzwerk zugänglich macht. - Cookies ohne
Sicherin der Entwicklung, auch in der Produktion verblieben: oft aus einer Testkonfiguration kopiert, die nie aktualisiert wurde. - JWT in localStorage „vorübergehend“ während der Entwicklung: wird fast immer dauerhaft, weil es „funktioniert“ und niemand es wieder sieht.
- Cache-Control fehlt standardmäßig auf neuen API-Routen: Ohne expliziten Header ist das Caching-Verhalten clientabhängig und nicht garantiert.
- s-maxage und max-age identisch: Der Vorteil, dass der Browser-Cache schneller ungültig gemacht werden kann als der Edge-Cache, entfällt.
- Keine maximale Größe für WebSocket-Nachrichten: Ein einzelner Client kann riesige Nutzlasten senden und den Serverspeicher überlasten.
- Widerruf, der nur zukünftige HTTP-Anfragen blockiert: Bestehende WebSocket-Verbindungen mit demselben kompromittierten Token am Leben lassen.
- Keine Überwachung von Cache-Hit/Miss: Eine sinkende Trefferquote deutet oft auf eine Regression in den Cache-Headern hin, die unbemerkt geblieben ist.
Schnelle Antworten
Warum ist HTTPS auch für WebSockets erforderlich? Ein Klartext-WebSocket (ws://) macht Sitzungstokens und Anwendungsnutzlasten für jeden im selben Netzwerk verfügbar; wss:// verschlüsselt die gesamte Verbindung genau wie HTTPS es für herkömmliche HTTP-Anfragen tut.
Was ist das SameSite-Flag eines Cookies? Steuert, ob ein Cookie in Cross-Site-Anfragen gesendet wird: Strict blockiert Cross-Site-Senden, Lax erlaubt es nur für die Navigation auf oberster Ebene, Keine erlaubt immer, erfordert aber Sicher.
Warum nicht ein JWT in localStorage speichern? Weil es von jedem auf der Seite ausgeführten JavaScript-Skript lesbar ist: Ein einzelnes XSS, selbst in einer Bibliothek eines Drittanbieters, ermöglicht die Exfiltration des Tokens ohne Benutzerinteraktion.
Was bedeutet Cache-Control: unveränderlich? Teilt dem Browser mit, dass sich der Inhalt dieser URL nie ändert, solange die URL gleich bleibt, wodurch sogar eine bedingte Validierungsanforderung während des Cache-Zeitraums vermieden wird.
Wie widerrufen Sie eine Sitzung mit aktiven WebSockets? Indem Sie den serverseitigen Status des Tokens (Blacklist oder Version) ungültig machen und jede mit diesem Benutzer verknüpfte WebSocket-Verbindung aktiv schließen, nicht nur neue HTTP-Anfragen ablehnen.
Was ist der Unterschied zwischen max-age und s-maxage? max-age überprüft den Browser-Cache des Benutzers, s-maxage überprüft freigegebene Caches wie CDN und Reverse-Proxy und hat, sofern vorhanden, Vorrang maximales Alter für diese Caches.
So überprüfen Sie
- Überprüfen Sie TLS- und HTTPS-Weiterleitungen:
curl -I https://your-domain.comund überprüfen Sie den HeaderStrict-Transport-Security. - Überprüfen Sie das TLS-Zertifikat und -Protokoll:
openssl s_client -connect your-domain.com:443 -tls1_2. - Überprüfen Sie die Cache-Header der Assets:
curl -I https://your-domain.com/main.a1b2c3d4.js, überprüfen SieimmutableundHöchstalter. - Überprüfen Sie, ob vertrauliche Antworten nicht zwischenspeicherbar sind:
curl -I https://your-domain.com/api/me, überprüfen Sieno-store. - Simulieren Sie die Last auf WebSocket, um die Ratenbegrenzung zu validieren:
npx artillery quick --count 100 -n 20 wss://your-domain.com/ws. - End-to-End-Widerruf testen: Widerrufen Sie eine Sitzung über die API und überprüfen Sie, ob der zugehörige WebSocket innerhalb weniger Sekunden geschlossen wird.
Fazit
Keine dieser zehn Vorgehensweisen allein reicht aus: TLS ohne richtig konfigurierte Cookies macht die Sitzung immer noch angreifbar, aggressives Caching ohne Unterscheidung von öffentlichen Antworten „private“ legt personenbezogene Daten offen, und eine Ratenbegrenzung ohne Überwachung lässt Sie nicht wissen, ob dies der Fall ist funktioniert wirklich. Gemeinsam angewendet, mit einem priorisierten Einführungsplan (HTTPS und Cookies zuerst, Caching und Ratenbegrenzung (also immer kontinuierliche Überwachung) bilden eine Grundlage für Sicherheit und Leistung konsistent für jede moderne Webanwendung mit Echtzeitkomponenten.
Möchten Sie eine ausdruckbare Checkliste oder Bewertung Ihrer Sicherheitseinrichtung? Anwendung? Fordern Sie ein technisches Audit an: In wenigen Stunden der Analyse ist es möglich, die zu identifizieren Prioritätslücken bei Cookies, Caching, WebSockets und Überwachung.