我使用过 websockets,也使用过 ELB,但我从来没有一起使用过它们,所以我没有意识到 Elastic Load Balancer 上的 HTTP 转发器不理解 websocket 请求......
所以我认为你必须使用 TCP 转发器,这解释了为什么你使用不同的端口,当然 TCP 转发器是协议不知道的,所以它根本不会添加任何标头。
一个看起来相当通用且简单的选项是让应用程序的 http 端通过推送信息而不是将其存储在缓存中以供检索来建议 websocket 端。它是可扩展且轻量级的,假设您的环境中没有障碍使其难以或不可能实施。
在生成加载 websocket 的网页时,获取字符串“ipv4:”和客户端的 IP(例如“192.168.1.1”),将它们连接并加密,并使结果对 url 友好:
/* pseudo-code */
base64_encode(aes_encrypt('ipv4:192.168.1.1','super_secret_key'))
使用具有 128 位 aes 的示例密钥和示例 IP 地址,我得到:
/* actual value returned by pseudo-code above */
1v5n2ybJBozw9Vz5HY5EDvXzEkcz2A4h1TTE2nKJMPk=
然后在为包含 websocket 的页面渲染 html 时,动态构建 url:
ws = new WebSocket('ws://example.com/sock?client=1v5n2ybJBozw9Vz5HY5EDvXzEkcz2A4h1TTE2nKJMPk=');
假设您的代码可以访问来自 websocket 的查询字符串,您可以使用超级密钥对查询参数“client”中找到的字符串进行 base64_decode 然后 aes_decrypt,然后验证它是否以“ipv4:”开头 ...如果它没有,那么它不是一个合法的价值。
当然,“ipv4:”(字符串开头)和“client”(查询参数)是任意选择,没有任何实际意义。我对 128 位 AES 的选择也是任意的。
当然,这种设置的问题在于它会被重放:给定的客户端 IP 地址将始终生成相同的值。如果您仅将客户端 IP 地址用于“信息目的”(例如日志记录或调试),那么这可能就足够了。如果您将它用于更重要的事情,您可能希望扩展此实现 - 例如,通过添加时间戳:
'ipv4:192.168.1.1;valid:1356885663;'
在接收端,解码字符串并检查时间戳。如果它不是 +/- 任何您认为安全的时间间隔(以秒为单位),那么请不要相信它。
这些建议都取决于您动态生成 websocket url 的能力、浏览器与其连接的能力,以及您能否访问 websocket 请求中 URL 的查询字符串部分......但如果这些部分到位,也许这会有所帮助。
其他想法(来自评论):
上面我建议的时间戳是从 epoch 开始的秒数,它为您提供了一个递增的计数器,它不需要您的平台中的状态——它只需要你的所有服务器时钟都是正确的——因此它不会增加不必要的复杂性。如果解密后的值包含与服务器当前时间相差小于(例如)5 秒 (+/-) 的时间戳,那么您就知道您正在处理经过身份验证的客户端。允许的时间间隔只需要与客户端在加载原始页面后尝试其 websocket 连接的最大合理时间一样长,再加上所有服务器时钟的最大偏差。
当然,使用 NAT,多个不同的用户可能在同一个源 IP 地址后面。这也是事实,尽管不太可能,用户实际上可以从与他们发起第一个 http 连接的源 IP 不同的源 IP 建立 websocket 连接,并且仍然是相当合法的......这听起来像是对您而言,身份验证用户可能比实际源 IP 更重要。
如果您在加密字符串中也包含经过身份验证的用户 ID,您将获得一个对原始 IP、用户帐户和时间唯一的值,精度为 1 秒。我认为这就是您所说的加盐。将用户帐户添加到字符串中应该可以获得您想要的信息。
'ipv4:192.168.1.1;valid:1356885663;memberid:32767;'
TLS 应防止未经授权的一方发现此加密字符串,但避免可重放性也很重要,因为生成的 URL 在用户浏览器的 html 页面“查看源”中以明文形式提供。您不希望今天获得授权但明天未经授权的用户能够使用应被识别为不再有效的签名字符串欺骗他们的方式。键入时间戳并要求它落在一个非常小的有效窗口中可以防止这种情况发生。