1

我们使用 WebSockets 与我们的 EC2 实例进行通信。我们的脚本使用 nodejs 和 Express 提供服务,然后初始化 WebSocket。现在使用 ELB,这使得识别客户端 IP 变得更加困难。使用 x-forwarded-for 标头我们可以在 HTTP 的上下文中获取 IP,但是当涉及到服务器中的 WebSocket 上下文时,它看起来好像不是由 Amazon 转发的。

我们确定了 2 个选项:

  1. 直接与实例通信 WebSocket(使用其公共 DNS)。
  2. 维护某种 sessionid,在其中存储在 HTTP 上下文中的 IP 并将其与 sessionid 关联。客户端将使用 HTTP 响应获取其 sessionid,并将其用于 WebSockets。服务器将识别客户端并从缓存中解析其 IP。

这两个选项都不是很好:1 不是容错的,2 是复杂的。还有更多的解决方案吗?亚马逊可以以某种方式转发 IP 吗?最佳做法是什么?

谢谢

4

2 回答 2

1

我使用过 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 页面“查看源”中以明文形式提供。您不希望今天获得授权但明天未经授权的用户能够使用应被识别为不再有效的签名字符串欺骗他们的方式。键入时间戳并要求它落在一个非常小的有效窗口中可以防止这种情况发生。

于 2012-12-30T16:55:34.147 回答
0

这取决于应用程序的严重程度。

根据客户端 IP 地址做出任何类型的决定都是一个冒险的提议。基于它的安全性,更是如此。虽然到目前为止所提供的建议在给定的限制条件下运行良好,但对于健壮的企业应用程序来说还不够。

正如已经指出的那样,客户端 IP 地址可能会被 NAT 掩盖。因此,从工作地点访问 Web 的人通常看起来具有相同的 IP 地址。人们家中的路由器充当 NAT,因此家中访问 Web 的每个家庭成员都将显示为具有相同的 IP 地址。甚至是同一个人从 PC 和平板电脑访问应用程序...

无论是否在 NAT 之后,在同一台机器上使用来自两个浏览器的应用程序都会显示为具有相同的地址。同样,同一浏览器中的多个选项卡似乎具有相同的地址。

代理或负载均衡器等其他连接点也可能隐藏原始客户端 IP 地址,以便代理/负载均衡器后面的东西认为它们是客户端。(更复杂或更低级别的中介可以防止这种情况,这使得它们更复杂或更昂贵。)

鉴于上述所有情况,一个严肃的应用程序不应该依赖客户端 IP 地址来做出任何重要的决定,尤其是在安全方面。

于 2013-01-01T22:43:35.347 回答