16. 安全性基础:WebSocket 加密(WSS)、消息校验、防止恶意连接
说实话,信令服务器这东西,很多人一开始都觉得——不就是个消息中转站嘛,能有什么安全问题?
我以前也这么想。直到有一次,我负责的一个WebRTC项目上线第二天,就被别人用脚本疯狂连接,服务器直接打满了。更尴尬的是,有人伪造了信令消息,把别人的视频流给劫走了。嗯,从那以后,我再也不敢轻视信令服务器的安全性了。
这一章,我们就来聊聊信令服务器安全的三板斧:WSS加密、消息校验、防止恶意连接。说白了,就是让你的信令服务器穿上防弹衣。
16.1 为什么信令服务器需要安全防护?
先想一个问题:信令服务器在WebRTC架构里扮演什么角色?
它是所有客户端交换连接信息的桥梁。SDP、ICE候选者、房间控制指令……这些数据一旦被篡改或泄露,后果很严重。
我遇到过最典型的攻击场景:
- 中间人攻击:明文WebSocket传输,数据包被截获,SDP被替换
- 消息伪造:攻击者模拟合法客户端,发送虚假的offer/answer
- 连接洪泛:大量伪造连接耗尽服务器资源,导致正常用户无法接入
所以,安全不是可选项,是必选项。
核心原则:信令服务器的安全设计,应该遵循“默认拒绝”策略——任何未经验证的连接和消息,一律拒绝。
16.2 WSS:给WebSocket穿上TLS外衣
WSS就是WebSocket over TLS。说白了,就是把WebSocket通信加密了。
我个人习惯,生产环境绝对不用WS,只用WSS。为什么?因为WS是明文传输,抓包工具一抓,你的SDP、ICE候选者全暴露了。
16.2.1 配置WSS服务器
以Node.js的ws库为例,配置WSS其实就多了一步:加载TLS证书。
const fs = require('fs');
const https = require('https');
const WebSocket = require('ws');
// 加载证书
const server = https.createServer({
cert: fs.readFileSync('/path/to/cert.pem'),
key: fs.readFileSync('/path/to/key.pem')
});
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws, req) => {
console.log('WSS连接已建立');
// 处理消息...
});
server.listen(443, () => {
console.log('WSS服务器运行在端口443');
});
这里要注意:证书必须是有效的。我见过有人用自签名证书上线,结果客户端连不上,因为浏览器不信任自签名证书。生产环境建议用Let's Encrypt,免费且自动续期。
小技巧:如果你用Nginx做反向代理,可以在Nginx层终止TLS,然后把明文WebSocket转发到后端。这样后端代码不用改,证书管理也方便。
16.2.2 客户端连接WSS
客户端连接WSS和连接WS几乎一样,只是把ws://换成wss://。
const socket = new WebSocket('wss://your-server.com/ws');
socket.onopen = () => {
console.log('WSS连接成功');
};
socket.onmessage = (event) => {
console.log('收到消息:', event.data);
};
嗯,就这么简单。但加密只是第一步,接下来我们还要解决消息可信的问题。
16.3 消息校验:确保消息来自合法客户端
WSS解决了“别人能不能偷看”的问题,但没解决“别人能不能冒充”的问题。
举个例子:攻击者虽然不知道你的SDP内容,但他可以伪造一条消息,说“我是用户A,我要和用户B通话”。如果服务器不校验,这条假消息就会被转发给用户B。
我曾经在调试一个项目时,发现有人用Postman直接往我的WebSocket发消息……嗯,当时我就意识到,必须加消息校验。
16.3.1 基于Token的校验
最常用的方案是:客户端连接时携带一个Token,服务器验证Token有效后才允许通信。
// 服务器端:验证Token
wss.on('connection', (ws, req) => {
const token = req.url.split('?token=')[1];
if (!token || !verifyToken(token)) {
ws.close(4001, 'Token无效');
return;
}
console.log('合法客户端连接');
// 继续处理...
});
function verifyToken(token) {
// 验证逻辑:比如JWT验证
// 返回true/false
}
客户端连接时,把Token放在URL参数里:
const token = 'your-jwt-token';
const socket = new WebSocket(`wss://your-server.com/ws?token=${token}`);
注意:Token不要放在URL里?其实WSS下URL是加密传输的,所以安全。但Token本身要有过期时间,防止泄露后被长期使用。
16.3.2 消息签名
除了连接时的Token校验,每条消息也可以加签名。这样即使Token泄露,攻击者也无法伪造消息内容。
// 客户端:发送带签名的消息
const message = {
type: 'offer',
target: 'userB',
sdp: '...'
};
const secret = 'shared-secret-key';
const signature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(message))
.digest('hex');
socket.send(JSON.stringify({
...message,
signature
}));
服务器收到后,验证签名:
// 服务器端:验证消息签名
wss.on('message', (data) => {
const msg = JSON.parse(data);
const { signature, ...payload } = msg;
const expectedSig = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
if (signature !== expectedSig) {
ws.close(4002, '消息签名无效');
return;
}
// 签名有效,处理消息
});
我个人建议:Token校验 + 消息签名一起用。Token保证连接合法,签名保证消息未被篡改。
16.4 防止恶意连接:别让服务器被拖垮
校验做好了,但还有一个问题:攻击者可以发起大量连接,即使每个连接都带Token,服务器也扛不住。
我记得有一次线上事故,就是被人用脚本开了几千个WebSocket连接,服务器CPU直接飙到100%。从那以后,我加了三道防线。
16.4.1 连接频率限制
限制每个IP在一定时间内的连接次数。
const rateLimit = new Map();
wss.on('connection', (ws, req) => {
const ip = req.socket.remoteAddress;
const now = Date.now();
if (!rateLimit.has(ip)) {
rateLimit.set(ip, []);
}
const timestamps = rateLimit.get(ip);
const recent = timestamps.filter(t => now - t < 60000); // 1分钟内
if (recent.length >= 10) { // 每分钟最多10次连接
ws.close(4003, '连接过于频繁');
return;
}
recent.push(now);
rateLimit.set(ip, recent);
// 继续处理...
});
16.4.2 最大连接数限制
设置服务器全局最大连接数,防止资源耗尽。
const MAX_CONNECTIONS = 1000;
let currentConnections = 0;
wss.on('connection', (ws, req) => {
if (currentConnections >= MAX_CONNECTIONS) {
ws.close(4004, '服务器繁忙');
return;
}
currentConnections++;
ws.on('close', () => {
currentConnections--;
});
// 继续处理...
});
16.4.3 心跳检测
有些恶意连接建立后不发送任何数据,就占着连接不释放。心跳检测可以及时清理这些僵尸连接。
const HEARTBEAT_INTERVAL = 30000; // 30秒
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', () => {
ws.isAlive = true;
});
const interval = setInterval(() => {
if (!ws.isAlive) {
ws.terminate();
clearInterval(interval);
return;
}
ws.isAlive = false;
ws.ping();
}, HEARTBEAT_INTERVAL);
ws.on('close', () => {
clearInterval(interval);
});
});
避坑指南:我曾经把心跳间隔设得太短(5秒),结果正常用户因为网络波动被频繁踢下线。建议30秒到60秒比较合理。
16.5 知识体系总览
下面这张图,把信令服务器安全的核心逻辑串起来了:
从这张图可以看得很清楚:客户端发起的连接,要经过WSS加密、连接控制、消息校验三层过滤,才能到达业务处理层。任何一层出问题,直接拒绝。
16.6 总结
信令服务器的安全,说白了就是三件事:
- 加密通信:用WSS代替WS,防止数据被窃听
- 校验身份:Token验证 + 消息签名,防止冒充和篡改
- 限制资源:频率限制、最大连接数、心跳检测,防止被拖垮
这三板斧用好了,你的信令服务器基本就稳了。当然,安全是一个持续对抗的过程,没有绝对的安全。但至少,别让攻击者太轻松。
嗯,这一章就到这里。记住:安全设计要前置,别等出事了再补。
一句话总结:WSS保加密,Token保身份,限流保资源——信令服务器安全三件套,缺一不可。