0%

WebSocket + Protobuf + 加密——实时通信安全方案

今天做核酸排队,想到 WebSocket 就像队伍一样,保持持续连接。结合 Protobuf 数据序列化和加密控制,可以有效防止第三方非法接入…

介绍

  随着实时通信需求的增长,WebSocket 已经成为现代 Web 应用中不可或缺的技术。然而,单纯的 WebSocket 连接在安全性方面存在一定的风险,容易受到第三方恶意接入和数据篡改等安全威胁。本文将详细介绍如何结合 WebSocket、Protobuf 和加密技术,构建一个安全可靠的实时通信系统。

WebSocket 基础概念

WebSocket 协议简介

  WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// WebSocket 基本使用示例 class WebSocketClient {
constructor(url) {
this.url = url;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = 1000;
}

connect() {
this.ws = new WebSocket(this.url);

this.ws.onopen = (event) => {
console.log('WebSocket connection established');
this.reconnectAttempts = 0; // 重置重连计数
};

this.ws.onmessage = (event) => {
console.log('Received message:', event.data);
this.handleMessage(event.data);
};

this.ws.onclose = (event) => {
console.log('WebSocket connection closed:', event.code, event.reason);
this.attemptReconnect();
};

this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}

sendMessage(message) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(Json.stringify(message));
} else {
console.error('WebSocket is not connected');
}
}

handleMessage(data) {
try {
const message = Json.parse(data);
// 处理消息逻辑 console.log('Processing message:', message);
} catch (error) {
console.error('Failed to parse message:', error);
}
}

attemptReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`Attempting to reconnect... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);

setTimeout(() => {
this.connect();
}, this.reconnectInterval * this.reconnectAttempts);
} else {
console.error('Max reconnection attempts reached');
}
}

close() {
if (this.ws) {
this.ws.close();
}
}
}

// 使用示例 const client = new WebSocketClient('ws://localhost:8080/ws');
client.connect();

// 发送消息 client.sendMessage({
type: 'chat',
data: 'Hello WebSocket!'
});

WebSocket 握手过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// WebSocket 握手请求示例
// 客户端发起握手 const ws = new WebSocket('ws://example.com/websocket', 'protocol1');

// 握手请求头
/*
GET /websocket HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
*/

// 服务器响应握手
/*
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
*/

// 安全的握手认证 class SecureWebSocket extends WebSocketClient {
constructor(url, authToken) {
super(url);
this.authToken = authToken;
}

connect() {
// 在 URL 中携带认证信息 const secureUrl = `${this.url}?token=${encodeURIComponent(this.authToken)}`;
this.ws = new WebSocket(secureUrl);

this.ws.onopen = (event) => {
console.log('Secure WebSocket connection established');
// 发送认证消息 this.sendAuthentication();
};

// 其他事件处理...
}

sendAuthentication() {
const authMessage = {
type: 'authenticate',
token: this.authToken,
timestamp: Date.now()
};

this.sendMessage(authMessage);
}
}

Protobuf 基础与应用

Protobuf 简介

  Protocol Buffers (Protobuf) 是 Google 开源的一种数据序列化格式,相比 Json 等文本格式,具有体积小、解析速度快、跨语言支持等优势。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// message.proto - Protobuf 定义文件 syntax = "proto3";

package com.example.messages;

// 消息类型枚举 enum MessageType {
UNKNOWN = 0;
CHAT = 1;
NOTIFICATION = 2;
HEARTBEAT = 3;
AUTH = 4;
SYSTEM = 5;
}

// 基础消息结构 message BaseMessage {
int64 timestamp = 1; // 时间戳 MessageType msg_type = 2; // 消息类型 string sender_id = 3; // 发送者 ID
string recipient_id = 4; // 接收者 ID(可选)
bytes payload = 5; // 消息负载 string signature = 6; // 数字签名 int32 sequence_num = 7; // 序列号
}

// 聊天消息 message ChatMessage {
string content = 1; // 聊天内容 int64 room_id = 2; // 房间 ID
repeated string mentions = 3; // 提及用户 Attachment attachment = 4; // 附件信息
}

// 附件信息 message Attachment {
string type = 1; // 附件类型 (image, file, etc.)
string url = 2; // 附件 URL
int64 size = 3; // 文件大小 string name = 4; // 文件名
}

// 认证消息 message AuthMessage {
string token = 1; // 认证令牌 string device_info = 2; // 设备信息 int64 user_id = 3; // 用户 ID
int64 timestamp = 4; // 时间戳
}

// 心跳消息 message HeartbeatMessage {
int64 client_timestamp = 1; // 客户端时间戳 int32 ping_interval = 2; // 心跳间隔 string status = 3; // 连接状态
}

Protobuf 在 Javascript 中的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 使用 protobuf.JS 库 const protobuf = require('protobufjs');

class ProtobufHandler {
constructor(protoPath) {
this.root = null;
this.loadProto(protoPath);
}

async loadProto(protoPath) {
try {
this.root = await protobuf.load(protoPath);
console.log('Protobuf schema loaded successfully');
} catch (error) {
console.error('Failed to load protobuf schema:', error);
}
}

// 编码消息 encodeMessage(type, data) {
if (!this.root) {
throw new Error('Protobuf schema not loaded');
}

const Message = this.root.lookupType(`com.example.messages.${type}`);
const message = Message.create(data);
const buffer = Message.encode(message).finish();

return buffer;
}

// 解码消息 decodeMessage(type, buffer) {
if (!this.root) {
throw new Error('Protobuf schema not loaded');
}

const Message = this.root.lookupType(`com.example.messages.${type}`);
const decoded = Message.decode(buffer);

return decoded;
}

// 创建基础消息 createBaseMessage(type, payload, senderId, recipientId = null) {
const payloadBuffer = this.encodeMessage(payload.type, payload.data);

const baseMessage = {
timestamp: Date.now(),
msg_type: type,
sender_id: senderId,
recipient_id: recipientId,
payload: payloadBuffer,
signature: '', // 签名将在加密后添加 sequence_num: this.generateSequenceNumber()
};

return baseMessage;
}

generateSequenceNumber() {
// 生成序列号,用于消息去重和排序 return Math.floor(Math.random() * 1000000);
}

// 安全编码消息(包含签名)
encodeSecureMessage(type, data, privateKey) {
const baseMessage = this.createBaseMessage(type, data);

// 生成消息签名 const messageBuffer = this.encodeMessage('BaseMessage', baseMessage);
const signature = this.signMessage(messageBuffer, privateKey);

// 更新签名 baseMessage.signature = signature;

return this.encodeMessage('BaseMessage', baseMessage);
}
}

// 使用示例 const pbHandler = new ProtobufHandler('./message.proto');

// 发送聊天消息 const chatPayload = {
type: 'ChatMessage',
data: {
content: 'Hello Protobuf!',
room_id: 123,
mentions: [],
attachment: null
}
};

const encodedMessage = pbHandler.encodeSecureMessage(
'CHAT',
chatPayload,
'private_key_here'
);

Protobuf 与 WebSocket 结合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// 结合 WebSocket 和 Protobuf 的消息处理器 class WebSocketProtobufClient extends WebSocketClient {
constructor(url, protoHandler) {
super(url);
this.pbHandler = protoHandler;
this.handlers = new Map();
this.setupMessageHandlers();
}

setupMessageHandlers() {
// 注册不同类型消息的处理器 this.handlers.set('CHAT', (message) => this.handleChatMessage(message));
this.handlers.set('HEARTBEAT', (message) => this.handleHeartbeat(message));
this.handlers.set('AUTH', (message) => this.handleAuthMessage(message));
this.handlers.set('SYSTEM', (message) => this.handleSystemMessage(message));
}

handleMessage(data) {
try {
// 假设接收到的是 Protobuf 编码的数据 const baseMessage = this.pbHandler.decodeMessage('BaseMessage', data);

// 验证消息完整性 if (!this.verifyMessage(baseMessage)) {
console.error('Message verification failed');
return;
}

// 根据消息类型分发处理 const messageType = this.getMessageTypeString(baseMessage.msg_type);
const handler = this.handlers.get(messageType);

if (handler) {
handler(baseMessage);
} else {
console.warn('Unknown message type:', messageType);
}
} catch (error) {
console.error('Failed to handle message:', error);
}
}

handleChatMessage(message) {
const chatData = this.pbHandler.decodeMessage('ChatMessage', message.payload);
console.log('Received chat message:', {
sender: message.sender_id,
content: chatData.content,
roomId: chatData.room_id
});
}

handleHeartbeat(message) {
const heartbeatData = this.pbHandler.decodeMessage('HeartbeatMessage', message.payload);
console.log('Heartbeat received:', heartbeatData.status);

// 回复心跳确认 this.sendHeartbeatAck();
}

handleAuthMessage(message) {
const authData = this.pbHandler.decodeMessage('AuthMessage', message.payload);
console.log('Auth message received:', {
userId: authData.user_id,
deviceId: authData.device_info
});
}

handleSystemMessage(message) {
console.log('System message:', message.payload.toString());
}

// 发送 Protobuf 编码的消息 sendProtobufMessage(messageType, messageData, recipientId = null) {
try {
const encodedMessage = this.pbHandler.encodeSecureMessage(
messageType,
messageData,
this.privateKey
);

if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(encodedMessage);
} else {
console.error('WebSocket is not connected');
}
} catch (error) {
console.error('Failed to send protobuf message:', error);
}
}

// 验证消息完整性 verifyMessage(message) {
// 检查消息签名 if (!this.verifySignature(message)) {
return false;
}

// 检查时间戳(防重放攻击)
if (Math.abs(Date.now() - message.timestamp) > 30000) { // 30秒 return false;
}

return true;
}

verifySignature(message) {
// 实现签名验证逻辑
// 这里简化为返回 true,实际应用中需要验证签名 return true;
}

getMessageTypeString(typeEnum) {
const typeMap = {
1: 'CHAT',
2: 'NOTIFICATION',
3: 'HEARTBEAT',
4: 'AUTH',
5: 'SYSTEM'
};
return typeMap[typeEnum] || 'UNKNOWN';
}
}

加密控制与安全防护

数据加密方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// 加密工具类 class CryptoUtils {
constructor() {
this.algorithm = 'AES-256-GCM';
}

// 生成密钥对 static generateKeyPair() {
const crypto = require('crypto');

// RSA 密钥对 const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});

return { publicKey, privateKey };
}

// AES 加密 static encryptAES(data, key, iv) {
const crypto = require('crypto');

const cipher = crypto.createCipheriv(this.algorithm, key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');

const authTag = cipher.getAuthTag();

return {
encrypted: encrypted,
authTag: authTag.toString('hex'),
iv: iv.toString('hex')
};
}

// AES 解密 static decryptAES(encryptedData, key, iv, authTag) {
const crypto = require('crypto');

const decipher = crypto.createDecipheriv(this.algorithm, key, Buffer.from(iv, 'hex'));
decipher.setAuthTag(Buffer.from(authTag, 'hex'));

let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');

return decrypted;
}

// 生成随机密钥 static generateRandomKey(length = 32) {
const crypto = require('crypto');
return crypto.randomBytes(length);
}

// 生成随机 IV
static generateRandomIV() {
const crypto = require('crypto');
return crypto.randomBytes(16); // GCM 模式需要16字节 IV
}

// 创建数字签名 static createSignature(data, privateKey) {
const crypto = require('crypto');

const signer = crypto.createSign('SHA256');
signer.update(data);
signer.end();

return signer.sign(privateKey, 'hex');
}

// 验证数字签名 static verifySignature(data, signature, publicKey) {
const crypto = require('crypto');

const verifier = crypto.createVerify('SHA256');
verifier.update(data);
verifier.end();

return verifier.verify(publicKey, signature, 'hex');
}

// 密钥协商 - Diffie-Hellman
static createDHSession() {
const crypto = require('crypto');

const alice = crypto.createDiffieHellman(2048);
alice.generateKeys();

return {
privateKey: alice.getPrivateKey(),
publicKey: alice.getPublicKey(),
computeSecret: (otherPublicKey) => {
return alice.computeSecret(otherPublicKey);
}
};
}
}

安全 WebSocket 连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// 安全的 WebSocket 客户端 class SecureWebSocketClient extends WebSocketProtobufClient {
constructor(url, protoHandler) {
super(url, protoHandler);
this.sessionKey = null;
this.ivCounter = 0;
this.authenticated = false;
this.trustedClients = new Set();

// 生成会话密钥 this.generateSessionKey();
}

generateSessionKey() {
this.sessionKey = CryptoUtils.generateRandomKey();
}

connect() {
// 在连接 URL 中添加安全参数 const secureUrl = `${this.url}?timestamp=${Date.now()}&nonce=${this.generateNonce()}`;
this.ws = new WebSocket(secureUrl);

this.ws.onopen = (event) => {
console.log('Secure WebSocket connection established');
// 发送初始认证 this.performInitialAuth();
};

this.ws.onmessage = (event) => {
// 接收加密数据 this.handleEncryptedMessage(event.data);
};

// 其他事件处理...
}

performInitialAuth() {
// 生成临时密钥用于初始认证 const { publicKey, privateKey } = CryptoUtils.generateKeyPair();

// 创建认证消息 const authMessage = {
type: 'AuthMessage',
data: {
token: this.getToken(),
device_info: this.getDeviceInfo(),
public_key: publicKey, // 用于密钥协商 timestamp: Date.now()
}
};

// 发送认证消息 this.sendProtobufMessage('AUTH', authMessage);
}

// 处理加密消息 handleEncryptedMessage(encryptedData) {
try {
// 解密数据 const decryptedData = this.decryptMessage(encryptedData);

// 验证消息完整性 if (!this.verifyMessageIntegrity(decryptedData)) {
console.error('Message integrity check failed');
return;
}

// 处理解密后的消息 this.handleMessage(decryptedData);
} catch (error) {
console.error('Failed to handle encrypted message:', error);
}
}

// 加密并发送消息 sendEncryptedMessage(messageType, messageData, recipientId = null) {
try {
// 编码消息 const baseMessage = this.pbHandler.createBaseMessage(
messageType,
messageData,
this.getClientId(),
recipientId
);

// 序列化消息 const serializedMessage = this.pbHandler.encodeMessage('BaseMessage', baseMessage);

// 加密消息 const iv = CryptoUtils.generateRandomIV();
const encryptedResult = CryptoUtils.encryptAES(
serializedMessage,
this.sessionKey,
iv
);

// 创建加密包装消息 const encryptedWrapper = {
type: 'EncryptedMessage',
data: {
encrypted_data: Buffer.from(encryptedResult.encrypted, 'hex'),
iv: Buffer.from(encryptedResult.iv, 'hex'),
auth_tag: Buffer.from(encryptedResult.authTag, 'hex'),
algorithm: 'AES-256-GCM'
}
};

// 发送加密包装消息 const encodedWrapper = this.pbHandler.encodeMessage('EncryptedMessage', encryptedWrapper.data);

if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(encodedWrapper);
} else {
console.error('WebSocket is not connected');
}
} catch (error) {
console.error('Failed to send encrypted message:', error);
}
}

// 解密消息 decryptMessage(encryptedData) {
try {
// 解码加密包装消息 const encryptedWrapper = this.pbHandler.decodeMessage('EncryptedMessage', encryptedData);

// 解密实际数据 const decryptedData = CryptoUtils.decryptAES(
encryptedWrapper.encrypted_data.toString('hex'),
this.sessionKey,
encryptedWrapper.iv.toString('hex'),
encryptedWrapper.auth_tag.toString('hex')
);

// 反序列化为 BaseMessage
const message = this.pbHandler.decodeMessage('BaseMessage', Buffer.from(decryptedData));
return message;
} catch (error) {
console.error('Decryption failed:', error);
throw error;
}
}

// 验证消息完整性 verifyMessageIntegrity(message) {
// 检查时间戳(防重放攻击)
const timeDiff = Math.abs(Date.now() - message.timestamp);
if (timeDiff > 30000) { // 30秒超时 return false;
}

// 检查序列号(防重放攻击)
if (!this.validateSequenceNumber(message.sequence_num)) {
return false;
}

// 检查消息来源是否可信 if (!this.trustedClients.has(message.sender_id)) {
return false;
}

return true;
}

validateSequenceNumber(sequenceNum) {
// 简单的序列号验证(实际应用中可能需要更复杂的逻辑)
// 检查是否是期望的序列号 const expectedSeq = this.expectedSequenceNumber || 0;

if (sequenceNum <= expectedSeq) {
// 可能是重放攻击 return false;
}

this.expectedSequenceNumber = sequenceNum;
return true;
}

// 防止第三方接入的措施 preventUnauthorizedAccess() {
// 1. IP 白名单 this.ipWhitelist = new Set(['127.0.0.1', '::1']);

// 2. 设备指纹验证 this.deviceVerification = {
userAgent: navigator.userAgent,
screenResolution: `${screen.width}x${screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
language: navigator.language
};

// 3. 频率限制 this.rateLimiting = {
messageLimit: 100, // 每分钟最大消息数 connectionLimit: 5, // 每 IP 最大连接数 windowMs: 60000 // 时间窗口(毫秒)
};
}

generateNonce() {
return Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
}

getToken() {
return localStorage.getItem('ws_auth_token');
}

getClientId() {
return localStorage.getItem('client_id');
}

getDeviceInfo() {
return Json.stringify(this.deviceVerification);
}
}

服务器端安全实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
// 安全 WebSocket 服务器端实现 const WebSocket = require('ws');
const http = require('http');

class SecureWebSocketServer {
constructor(options = {}) {
this.options = options;
this.wss = null;
this.clients = new Map(); // 客户端映射 this.authTokens = new Map(); // 认证令牌映射 this.sessionKeys = new Map(); // 会话密钥映射 this.start();
}

start() {
const server = http.createServer();
this.wss = new WebSocket.Server({ server });

this.wss.on('connection', (ws, req) => {
const clientId = this.generateClientId(req);

// 验证连接参数 if (!this.validateConnection(req)) {
ws.close(4001, 'Invalid connection parameters');
return;
}

// 存储客户端信息 this.clients.set(clientId, {
ws: ws,
id: clientId,
authenticated: false,
lastActivity: Date.now(),
ip: req.socket.remoteAddress
});

this.setupClientListeners(ws, clientId);

// 发送欢迎消息 this.sendWelcomeMessage(ws, clientId);
});

server.listen(this.options.port || 8080, () => {
console.log(`Secure WebSocket server listening on port ${this.options.port || 8080}`);
});
}

setupClientListeners(ws, clientId) {
ws.on('message', (data) => {
this.handleClientMessage(ws, clientId, data);
});

ws.on('close', () => {
this.handleClientDisconnect(clientId);
});

ws.on('error', (error) => {
console.error(`WebSocket error for client ${clientId}:`, error);
this.handleClientError(clientId, error);
});
}

validateConnection(req) {
// 验证请求参数 const urlParams = new URLSearchParams(req.url.split('?')[1]);
const timestamp = parseInt(urlParams.get('timestamp'));
const nonce = urlParams.get('nonce');

// 检查时间戳(防重放攻击)
if (Math.abs(Date.now() - timestamp) > 30000) {
return false;
}

// 验证 nonce(防重放攻击)
if (!this.isValidNonce(nonce)) {
return false;
}

return true;
}

handleClientMessage(ws, clientId, data) {
const client = this.clients.get(clientId);

if (!client) {
ws.close(4002, 'Client not found');
return;
}

try {
// 如果客户端未认证,只允许认证消息 if (!client.authenticated) {
this.handleInitialAuth(ws, clientId, data);
return;
}

// 解密消息 const decryptedMessage = this.decryptClientMessage(data, clientId);

// 验证消息 if (!this.validateClientMessage(decryptedMessage, clientId)) {
ws.close(4003, 'Invalid message');
return;
}

// 处理消息 this.processClientMessage(ws, clientId, decryptedMessage);
} catch (error) {
console.error(`Error processing message from client ${clientId}:`, error);
ws.close(4004, 'Message processing error');
}
}

async handleInitialAuth(ws, clientId, data) {
try {
// 解码认证消息 const authMessage = this.pbHandler.decodeMessage('BaseMessage', data);
const authPayload = this.pbHandler.decodeMessage('AuthMessage', authMessage.payload);

// 验证认证令牌 if (!this.verifyAuthToken(authPayload.token)) {
ws.close(4005, 'Invalid auth token');
return;
}

// 验证设备信息 if (!this.verifyDevice(authPayload.device_info)) {
ws.close(4006, 'Invalid device');
return;
}

// 标记客户端为已认证 const client = this.clients.get(clientId);
client.authenticated = true;
client.userId = authPayload.user_id;

// 生成会话密钥 const sessionKey = CryptoUtils.generateRandomKey();
this.sessionKeys.set(clientId, sessionKey);

// 发送认证成功消息 this.sendAuthSuccess(ws, clientId, sessionKey);

console.log(`Client ${clientId} authenticated successfully`);
} catch (error) {
console.error('Authentication failed:', error);
ws.close(4007, 'Authentication failed');
}
}

sendAuthSuccess(ws, clientId, sessionKey) {
// 创建认证成功消息 const successMessage = {
type: 'AUTH_SUCCESS',
data: {
session_key_encrypted: this.encryptSessionKey(sessionKey, clientId),
welcome_msg: 'Authentication successful',
server_time: Date.now()
}
};

const encodedMessage = this.pbHandler.encodeMessage('AuthSuccessMessage', successMessage.data);
ws.send(encodedMessage);
}

validateClientMessage(message, clientId) {
// 验证消息来源 if (message.sender_id !== clientId) {
return false;
}

// 验证时间戳 if (Math.abs(Date.now() - message.timestamp) > 30000) {
return false;
}

// 验证序列号(防重放攻击)
if (!this.validateMessageSequence(message, clientId)) {
return false;
}

return true;
}

validateMessageSequence(message, clientId) {
const client = this.clients.get(clientId);
const lastSeq = client.lastMessageSeq || 0;

if (message.sequence_num <= lastSeq) {
return false; // 重复消息
}

client.lastMessageSeq = message.sequence_num;
return true;
}

processClientMessage(ws, clientId, message) {
// 更新最后活动时间 const client = this.clients.get(clientId);
client.lastActivity = Date.now();

// 根据消息类型进行处理 switch (message.msg_type) {
case 1: // CHAT
this.handleChatMessage(ws, clientId, message);
break;
case 3: // HEARTBEAT
this.handleHeartbeat(ws, clientId, message);
break;
case 5: // SYSTEM
this.handleSystemMessage(ws, clientId, message);
break;
default:
console.warn(`Unknown message type: ${message.msg_type}`);
}
}

handleChatMessage(ws, clientId, message) {
// 广播消息到其他客户端 const recipients = this.getClientsForBroadcast(clientId);

recipients.forEach(recipient => {
if (recipient.ws.readyState === WebSocket.OPEN) {
recipient.ws.send(message.payload);
}
});
}

handleHeartbeat(ws, clientId, message) {
// 回复心跳 const heartbeatReply = {
type: 'HeartbeatMessage',
data: {
client_timestamp: message.timestamp,
server_timestamp: Date.now(),
status: 'OK'
}
};

const encodedReply = this.pbHandler.encodeMessage('HeartbeatMessage', heartbeatReply.data);
ws.send(encodedReply);
}

handleSystemMessage(ws, clientId, message) {
// 系统消息处理 console.log(`System message from ${clientId}:`, message.payload.toString());
}

getClientsForBroadcast(senderId) {
return Array.from(this.clients.values())
.filter(client =>
client.authenticated &&
client.id !== senderId &&
client.ws.readyState === WebSocket.OPEN
);
}

handleClientDisconnect(clientId) {
const client = this.clients.get(clientId);
if (client) {
console.log(`Client ${clientId} disconnected`);
this.clients.delete(clientId);
this.sessionKeys.delete(clientId);
}
}

handleClientError(clientId, error) {
console.error(`Client ${clientId} error:`, error);
const client = this.clients.get(clientId);
if (client) {
client.ws.close(4008, 'Client error');
this.handleClientDisconnect(clientId);
}
}

// 加密相关方法 decryptClientMessage(data, clientId) {
const sessionKey = this.sessionKeys.get(clientId);
if (!sessionKey) {
throw new Error('No session key found');
}

// 解码加密包装消息 const encryptedWrapper = this.pbHandler.decodeMessage('EncryptedMessage', data);

// 解密实际数据 const decryptedData = CryptoUtils.decryptAES(
encryptedWrapper.encrypted_data.toString('hex'),
sessionKey,
encryptedWrapper.iv.toString('hex'),
encryptedWrapper.auth_tag.toString('hex')
);

return this.pbHandler.decodeMessage('BaseMessage', Buffer.from(decryptedData));
}

encryptSessionKey(sessionKey, clientId) {
// 这里简化处理,实际应用中需要使用客户端的公钥加密 return sessionKey.toString('hex');
}

// 辅助方法 generateClientId(req) {
return `${req.socket.remoteAddress}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

isValidNonce(nonce) {
// 简单的 nonce 验证(实际应用中需要更严格的验证)
if (!nonce || nonce.length < 10) {
return false;
}

// 记录 nonce 以防止重用 if (!this.usedNonces) {
this.usedNonces = new Set();
}

if (this.usedNonces.has(nonce)) {
return false; // nonce 已被使用
}

this.usedNonces.add(nonce);
return true;
}

verifyAuthToken(token) {
// 验证认证令牌的有效性 return this.authTokens.has(token) &&
Date.now() < this.authTokens.get(token).expiresAt;
}

verifyDevice(deviceInfo) {
// 验证设备信息(实际应用中可能需要更详细的验证)
try {
const parsed = Json.parse(deviceInfo);
return parsed.userAgent && parsed.screenResolution;
} catch {
return false;
}
}

sendWelcomeMessage(ws, clientId) {
const welcomeMessage = {
type: 'BaseMessage',
data: {
timestamp: Date.now(),
msg_type: 5, // SYSTEM
sender_id: 'server',
recipient_id: clientId,
payload: Buffer.from('Welcome to secure WebSocket server'),
signature: '',
sequence_num: 0
}
};

const encodedMessage = this.pbHandler.encodeMessage('BaseMessage', welcomeMessage.data);
ws.send(encodedMessage);
}
}

// 启动安全 WebSocket 服务器 const secureServer = new SecureWebSocketServer({
port: 8080,
protoPath: './message.proto'
});

防止第三方接入的最佳实践

客户端验证机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// 客户端验证工具 class ClientValidator {
constructor() {
this.validationRules = {
userAgent: /Mozilla\/5\.0/,
platform: ['Win32', 'MacIntel', 'Linux x86_64'],
plugins: ['Chrome PDF Plugin', 'Chrome PDF Viewer'],
languages: navigator.languages || [navigator.language],
screen: {
width: screen.width,
height: screen.height,
colorDepth: screen.colorDepth
},
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
canvasFingerprint: null
};

this.canvasFingerprint = this.generateCanvasFingerprint();
}

// 生成画布指纹 generateCanvasFingerprint() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

// 绘制一些内容来生成独特的指纹 ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillStyle = '#f60';
ctx.fillRect(125, 1, 62, 20);
ctx.fillStyle = '#069';
ctx.fillText('Hello, world! 😊', 2, 15);
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
ctx.font = '18px Arial';
ctx.fillText('Hello, world! 😊', 4, 16.5);

return canvas.toDataURL();
}

// 验证客户端指纹 validateClientFingerprint() {
const currentFingerprint = this.generateCanvasFingerprint();

// 检查是否与已知指纹匹配 if (currentFingerprint !== this.canvasFingerprint) {
console.error('Client fingerprint mismatch - possible unauthorized access');
return false;
}

// 检查浏览器环境 if (!this.validateBrowserEnvironment()) {
return false;
}

return true;
}

// 验证浏览器环境 validateBrowserEnvironment() {
// 检查关键的浏览器 API
const requiredApis = [
'WebSocket',
'localStorage',
'sessionStorage',
'indexedDB',
'fetch',
'crypto'
];

for (const API of requiredApis) {
if (!(API in window)) {
return false;
}
}

// 检查 User Agent
const ua = navigator.userAgent.toLowerCase();
if (!ua.includes('mozilla') || !ua.includes('webkit')) {
return false;
}

// 检查平台 if (!this.validationRules.platform.includes(navigator.platform)) {
return false;
}

return true;
}

// 生成客户端签名 generateClientSignature() {
const fingerprintData = {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
screen: {
width: screen.width,
height: screen.height,
colorDepth: screen.colorDepth,
pixelDepth: screen.pixelDepth
},
timezone: this.validationRules.timezone,
canvasFingerprint: this.canvasFingerprint,
timestamp: Date.now()
};

// 创建签名(实际应用中应使用更安全的方法)
return btoa(Json.stringify(fingerprintData)).substring(0, 64);
}
}

服务端验证实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// 服务端验证实现 class ServerSideValidator {
constructor() {
this.allowedOrigins = new Set([
'https://yourdomain.com',
'https://www.yourdomain.com'
]);

this.rateLimits = new Map(); // 存储频率限制 this.sessionTracker = new Map(); // 会话跟踪
}

// 验证 WebSocket 握手请求 validateHandshake(req) {
// 验证 Origin
const origin = req.headers.origin;
if (origin && !this.allowedOrigins.has(origin)) {
return {
valid: false,
reason: 'Invalid origin'
};
}

// 验证 User-Agent(防爬虫)
const userAgent = req.headers['user-agent'];
if (!userAgent || userAgent.includes('bot') || userAgent.includes('crawler')) {
return {
valid: false,
reason: 'Suspicious user agent'
};
}

// 验证频率限制 const clientIp = this.getClientIp(req);
if (this.isRateLimited(clientIp)) {
return {
valid: false,
reason: 'Rate limit exceeded'
};
}

// 更新频率限制计数 this.updateRateLimit(clientIp);

return {
valid: true,
reason: 'Valid connection'
};
}

// 验证客户端签名 validateClientSignature(signature, expectedSignature) {
// 验证签名是否匹配 if (signature !== expectedSignature) {
return false;
}

// 检查签名时间戳(防重放攻击)
const parts = signature.split(':');
if (parts.length >= 2) {
const timestamp = parseInt(parts[parts.length - 1]);
const timeDiff = Math.abs(Date.now() - timestamp);

if (timeDiff > 30000) { // 30秒有效期 return false;
}
}

return true;
}

// 检查 IP 频率限制 isRateLimited(ip) {
const now = Date.now();
const limits = this.rateLimits.get(ip);

if (!limits) {
return false;
}

// 检查连接频率 const connectionLimit = 5; // 每分钟最多5次连接 const timeWindow = 60000; // 1分钟窗口 const recentConnections = limits.connections.filter(time => now - time < timeWindow);

return recentConnections.length >= connectionLimit;
}

// 更新频率限制 updateRateLimit(ip) {
const now = Date.now();

if (!this.rateLimits.has(ip)) {
this.rateLimits.set(ip, { connections: [] });
}

const limits = this.rateLimits.get(ip);
limits.connections.push(now);

// 清理过期记录 limits.connections = limits.connections.filter(time => now - time < 60000);
}

// 获取客户端 IP
getClientIp(req) {
return req.headers['x-forwarded-for'] ||
req.headers['x-real-ip'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
(req.connection.socket ? req.connection.socket.remoteAddress : null);
}

// 验证 JWT 令牌 async validateJWTToken(token) {
try {
// 这里应该使用实际的 JWT 验证逻辑 const jwt = require('jsonwebtoken');

// 验证令牌 const decoded = jwt.verify(token, process.env.JWT_SECRET);

// 检查令牌是否在黑名单中 if (this.isTokenBlacklisted(token)) {
return { valid: false, reason: 'Token blacklisted' };
}

return { valid: true, user: decoded };
} catch (error) {
return { valid: false, reason: 'Invalid token' };
}
}

// 检查令牌是否被拉黑 isTokenBlacklisted(token) {
// 实际应用中应该查询数据库或缓存 return false;
}
}

总结

  • WebSocket 提供了全双工实时通信能力,但需要安全防护措施
  • Protobuf 提供了高效的二进制序列化方案,减少传输开销
  • 加密技术保障了数据传输的安全性,防止窃听和篡改
  • 多层安全验证机制可以有效防止第三方非法接入
  • 客户端和服务端都需要实施相应的安全措施
  • 频率限制和防重放攻击是重要的安全实践
  • 完整的认证流程确保只有合法客户端可以建立连接

今天排队做核酸的经历让我想到 WebSocket 的连接特性,就像排队一样,需要有序管理并且防止插队(第三方接入)。结合 Protobuf 和加密技术,我们可以构建一个安全可靠的实时通信系统,就像有序的核酸检测队伍一样,确保每个连接都是经过验证和授权的。

安全建议

  1. 始终使用 WSS(WebSocket Secure)而不是 WS 协议
  2. 实施严格的频率限制以防止滥用
  3. 定期轮换密钥和令牌
  4. 实施完善的日志记录和监控
  5. 定期进行安全审计和渗透测试
  6. 保持依赖库的及时更新

扩展阅读

  1. WebSocket 协议规范
  2. Protocol Buffers 官方文档
  3. 网络安全最佳实践
  4. 现代加密技术指南
bulb