Node.js WebSocket实时笔记服务器遭DDoS攻击,求防护方案
Got it, let's tackle this WebSocket abuse issue you're facing. It’s super frustrating when bad actors spam garbage data to take down your app—here are several practical, actionable steps tailored to your Node.js + MongoDB setup:
1. Filter Garbage Message Content First
Start with the simplest check: block messages that match the exact pattern of your attacker’s spam (all 'a's). This stops the garbage at the door before it even reaches your database.
Here’s how to implement it with the ws library:
const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', (ws) => { ws.on('message', (data) => { const message = data.toString().trim(); // Reject messages that are only repeated 'a's if (/^a+$/.test(message)) { // Close the connection with a "policy violation" code ws.close(1008, 'Invalid message content'); return; } // Proceed with your normal message processing/sync logic... }); });
2. Add Rate Limiting Per Client
Malicious actors rely on sending tons of requests quickly. Implement rate limiting to cap how many messages a single client (or IP) can send in a window of time.
You can track message counts with a Map:
const clientRateLimits = new Map(); // key: client IP, value: { count: number, resetTime: number } wss.on('connection', (ws, req) => { const clientIP = ws._socket.remoteAddress; ws.on('message', (data) => { const now = Date.now(); const limitData = clientRateLimits.get(clientIP) || { count: 0, resetTime: now + 10000 }; // 10-second window // Reset count if window has passed if (now > limitData.resetTime) { limitData.count = 1; limitData.resetTime = now + 10000; } else { limitData.count++; // Block if over 50 messages in 10 seconds (adjust based on your app's normal usage) if (limitData.count > 50) { ws.close(1008, 'Rate limit exceeded'); clientRateLimits.delete(clientIP); return; } } clientRateLimits.set(clientIP, limitData); // Process valid messages... }); // Clean up when client disconnects ws.on('close', () => { clientRateLimits.delete(clientIP); }); });
3. Enforce Authentication for WebSocket Connections
Since your app supports multi-device login sync, you already have user authentication—extend that to WebSocket connections. Require clients to send a valid auth token (like JWT) when connecting, and reject unauthenticated connections entirely.
Example with JWT verification:
const jwt = require('jsonwebtoken'); wss.on('connection', (ws, req) => { // Extract token from request headers or query params const authHeader = req.headers['authorization']; const token = authHeader?.split(' ')[1]; // Expecting "Bearer <token>" if (!token) { ws.close(1008, 'Authentication required'); return; } // Verify the token jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { if (err) { ws.close(1008, 'Invalid authentication'); return; } // Attach user data to the WebSocket for future reference ws.user = decoded; }); // In subsequent message handlers, check if ws.user exists before processing ws.on('message', (data) => { if (!ws.user) { ws.close(1008, 'Unauthenticated'); return; } // Process valid, authenticated messages... }); });
4. Temporarily Ban Malicious IPs
If an IP repeatedly sends garbage data, ban it for a set period to prevent further abuse. Use a Map to track banned IPs and their unban times:
const bannedIPs = new Map(); // key: IP, value: unban timestamp (ms) wss.on('connection', (ws, req) => { const clientIP = ws._socket.remoteAddress; // Check if IP is currently banned const unbanTime = bannedIPs.get(clientIP); if (unbanTime && Date.now() < unbanTime) { ws.close(1008, 'IP temporarily banned'); return; } ws.on('message', (data) => { const message = data.toString().trim(); if (/^a+$/.test(message)) { // Ban the IP for 1 hour (adjust as needed) bannedIPs.set(clientIP, Date.now() + 3600000); ws.close(1008, 'Malicious activity detected'); return; } // Process valid messages... }); });
5. Add a Reverse Proxy for Extra Protection
Put a reverse proxy like Nginx or Cloudflare in front of your WebSocket server. This lets you handle rate limiting, connection limits, and even WAF rules before requests hit your Node.js app.
For example, here’s a snippet of Nginx config to limit connections and request rates:
location /ws { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Limit each IP to 10 concurrent WebSocket connections limit_conn addr 10; # Allow 50 requests per second, with a burst of 20 limit_req zone=ws burst=20 nodelay; } # Define the rate limit zone limit_req_zone $binary_remote_addr zone=ws:10m rate=50r/s;
6. Protect MongoDB from Invalid Writes
Even if some garbage slips through, add a last line of defense at the database level. Validate content before writing to MongoDB, and reject any entries that match the spam pattern:
// Example Mongoose schema with validation const noteSchema = new mongoose.Schema({ userId: { type: String, required: true }, content: { type: String, required: true, validate: { validator: function(v) { // Reject content that's only repeated 'a's return !/^a+$/.test(v.trim()); }, message: props => `${props.value} is invalid note content!` } } }); const Note = mongoose.model('Note', noteSchema); // When saving, the validation will automatically throw an error for garbage data async function saveUserNote(userId, content) { try { await Note.create({ userId, content }); } catch (err) { // Handle validation error (log it, notify admins, etc.) console.error('Failed to save note:', err.message); } }
Final Tip: Combine Multiple Layers
For the best protection, use a mix of these methods—authentication blocks unregistered clients, content filtering stops known spam, rate limiting slows down attackers, and reverse proxies add a robust outer layer.
内容的提问来源于stack exchange,提问作者user8438001

