WebSocket多租户隔离关键在于服务端按租户组织连接与广播:连接时通过URL路径、Header或JWT传递并校验tenant_id;内存用Map<string, Set<WebSocket>>或Redis按租户前缀隔离;推荐Socket.IO的Namespace分租户+Room分模块;须绑定不可变上下文并校验消息目标归属。

WebSocket 在多租户 SaaS 平台中实现租户消息隔离,关键不在于 WebSocket 协议本身(它本身无租户概念),而在于服务端如何组织连接、路由、广播和存储逻辑。核心思路是:**每个租户拥有独立的通信上下文,且消息流转严格限定在该上下文中**。
租户标识必须在连接建立初期就明确
客户端发起 WebSocket 连接时,需携带可验证的租户身份信息。常见方式有:
-
URL 路径参数:如
wss://api.example.com/ws/tenant-a,服务端从websocket.path提取tenant-a -
请求头(Header):如自定义
X-Tenant-ID: tenant-b,需在握手阶段透传(Nginx 配置中确保proxy_set_header X-Tenant-ID $http_x_tenant_id;) -
Token 认证(推荐):客户端在 URL 中带 JWT(如
wss://.../ws?token=eyJhb...),服务端解码后校验签名、有效期,并提取其中声明的tenant_id字段
⚠️ 注意:不能依赖 Cookie 或 Session,因 WebSocket 握手是 HTTP 请求,但后续帧不携带 Cookie;且多租户场景下 Session 共享易引发混淆。
服务端按租户组织连接池与广播域
收到连接后,服务端应立即将该 socket 归属到对应租户的“容器”中,避免跨租户混用。具体做法包括:
立即学习“Java免费学习笔记(深入)”;
-
内存映射结构:用
Map<string, Set<WebSocket>>存储,键为tenantId,值为该租户所有活跃连接集合 -
Redis 支持分布式部署:使用 Redis 的
PUB/SUB或Stream按租户前缀隔离,例如频道名ws:tenant-c:chat;或用 Hash 结构存连接元数据:HSET ws:connections:tenant-d socket123 userId:U789 -
拒绝跨租户广播:当收到某租户内用户发来的消息,只向
tenantId对应的连接集合广播,绝不调用全局io.emit()或wss.broadcast()
借助命名空间(Namespace)或房间(Room)做逻辑分组
若使用 Socket.IO(基于 WebSocket 的封装库),可天然利用其分层机制提升隔离清晰度:
-
按租户划分 Namespace:每个租户一个独立命名空间,如
/tenant-e。客户端连接时指定:io('/tenant-e');服务端监听也限定范围:io.of('/tenant-e').on('connection', ...) -
在 Namespace 内再建 Room:如按业务模块细分
chat、notification、live-dashboard等房间,进一步控制消息可见范围 - 优势:自动隔离事件监听、中间件作用域、连接统计,降低手动管理出错概率
权限与消息内容层面的二次校验
仅靠路由隔离还不够,需防止恶意客户端伪造租户 ID 或越权操作:
-
连接时绑定不可变上下文:将
tenantId、userId、role等可信字段存入 socket 实例属性(如socket.data.tenantId = 'tenant-f'),后续所有消息处理都读取该属性,而非重新解析请求 -
消息体校验:若消息中包含目标用户 ID 或资源 ID(如
{to: "U123", content: "hi"}),服务端需确认该用户确实属于当前租户(查数据库或缓存),否则丢弃 - 敏感操作拦截:对删除、导出、配置变更等高危动作,强制要求额外权限令牌或二次确认,不依赖前端传参


















