WebSocket 作为现代实时通信的核心技术,其鉴权机制的设计直接影响着应用的安全性和用户体验。与传统的 HTTP 请求不同,WebSocket 连接一旦建立就会保持长时间的持久连接,这使得传统的基于请求-响应的鉴权模式不再适用。
在 HTTP 协议中,每个请求都是独立的,服务器可以在处理每个请求时进行鉴权。这种无状态的特性使得鉴权逻辑相对简单直接。但 WebSocket 的工作机制完全不同:
这种特性带来了几个关键挑战:
在设计 WebSocket 鉴权方案时,我们需要防范以下几类安全威胁:
中间人攻击:攻击者可能拦截 WebSocket 连接,窃取或篡改通信内容。解决方案是强制使用 WSS(WebSocket Secure)协议,即基于 TLS 加密的 WebSocket。
会话劫持:如果鉴权令牌被泄露,攻击者可以冒充合法用户。这要求我们:
重放攻击:攻击者可能捕获并重复发送有效的请求。防御措施包括:
Electron 应用作为桌面客户端,有其特殊的运行环境和安全需求:
本地存储安全:
多进程架构:
离线能力:
这种方案将鉴权过程分为两个阶段:
典型的消息格式如下:
json复制{
"action": "auth",
"token": "eyJhbGciOiJIUzI1NiIs...",
"timestamp": 1630000000
}
服务器收到鉴权消息后验证令牌的有效性,如果验证通过则标记该连接为已认证状态,否则关闭连接。
优势:
劣势:
适合:
不适合:
这种方案引入了两个令牌:
工作流程:
客户端实现要点:
javascript复制class TokenManager {
private accessToken: string;
private refreshToken: string;
async refreshTokens() {
try {
const response = await fetch('/auth/refresh', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.refreshToken}`
}
});
const { accessToken, refreshToken } = await response.json();
this.storeTokens(accessToken, refreshToken);
} catch (error) {
this.handleRefreshError();
}
}
private scheduleRefresh(expiresIn: number) {
// 在过期前5分钟触发刷新
setTimeout(() => this.refreshTokens(), (expiresIn - 300) * 1000);
}
}
在双令牌基础上增加:
典型鉴权消息:
json复制{
"action": "auth",
"token": "eyJ...",
"deviceId": "abc123",
"nonce": "xyz789",
"signature": "a1b2c3...",
"timestamp": 1630000000
}
javascript复制function generateSignature(token, message) {
const hmac = crypto.createHmac('sha256', token);
hmac.update(JSON.stringify(message));
return hmac.digest('hex');
}
跨平台安全存储方案:
typescript复制import { safeStorage } from 'electron';
class SecureStorage {
encrypt(text: string): Buffer {
return safeStorage.encryptString(text);
}
decrypt(buffer: Buffer): string {
return safeStorage.decryptString(buffer);
}
}
断线重连策略:
javascript复制class WebSocketClient {
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
connect() {
this.ws = new WebSocket(url);
this.ws.onclose = () => {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000);
setTimeout(() => {
this.reconnectAttempts++;
this.connect();
}, delay);
}
};
}
}
设备唯一标识生成:
javascript复制function getDeviceId() {
let id = localStorage.getItem('deviceId');
if (!id) {
id = crypto.randomUUID();
localStorage.setItem('deviceId', id);
}
return id;
}
对于需要维护多个WebSocket连接的应用,建议:
对于高频消息场景:
javascript复制// 发送端
const compressed = pako.deflate(JSON.stringify(message));
ws.send(compressed);
// 接收端
ws.on('message', (data) => {
const decompressed = pako.inflate(data, { to: 'string' });
const message = JSON.parse(decompressed);
});
错误代码标准化:
json复制{
"code": "AUTH_1001",
"message": "Token expired",
"action": "refresh_token"
}
客户端处理逻辑:
javascript复制ws.on('message', (message) => {
if (message.code === 'AUTH_1001') {
tokenManager.refresh();
}
});
解决方案:
Electron中可配置:
javascript复制webPreferences: {
webSecurity: false,
allowRunningInsecureContent: true
}
生产环境应该:
技术栈选择:
消息处理器:
typescript复制class MessageHandler {
private connections = new Map<string, WebSocket>();
handleAuth(ws: WebSocket, message: AuthMessage) {
try {
const payload = verifyToken(message.token);
ws.userId = payload.userId;
this.connections.set(payload.userId, ws);
} catch (error) {
ws.close(1008, 'Auth failed');
}
}
}
测试环境:
测试指标:
新一代传输协议特点:
发展趋势:
优化方向: