java 实现 websocket 鉴权、安全、心跳检测

2023-02-09 宋洋葱 宋洋葱

前言

http 协议是一种“被动式”的协议,只能通过 client 主动找 server 拉取数据,无法做到数据变化 server 立刻给 client 主动推送数据。使用 WebSocket 长链接协议可以优雅的解决服务端主动给客户端推送数据的问题。

实现 websocket 鉴权、安全、心跳检测

1. 鉴权 websocket 协议类似 http 协议,只是一个为了解决全双工通行的问题。对于鉴权相关的部分,RFC 6455中是这么描述的:

This protocol doesn’t prescribe any particular way that servers can authenticate clients during the WebSocket handshake. The WebSocket server can use any client authentication mechanism available to a generic HTTP server, such as cookies, HTTP authentication, or TLS authentication.

也就是 websocket 协议的鉴权得我们自己实现。

那么 websocket 是否可以类似 http 一样在 header 发送 cookie 或者其他请求头(如 Authorization)来鉴权呢?

答案是不可以,只有 pathprotocol 可以被指定。

开发者只可以指定 WebSocket 中的请求路径 (“GET /xyz”) 和协议 (“Sec-WebSocket-Protocol”)

既然可以指定路径,那在请求路径中使用参数来鉴权可行吗?当然可行,服务端实现一个 ticket ,客户端在建立连接后,发送第一条信息时,在 URL 或 query string 中传递这个 ticket,服务端检查其是否有效。这种方式的缺点是 ticket 暴露在 url 中,不是很安全。

查看 WebSocket 建立连接的过程可概括为三步:

  1. 通过 http 协议给 server 发送升级 websocket 的报文。
  2. server 支持升级,响应 http 协议返回一个 201
  3. websocket 建立连接

websocket 协议不支持自定义请求头,在握手前使用 http 协议的请求头完成鉴权似乎是一种更好的方式。

解决浏览器端连接数 255 限制

  1. 业务规划时不超过 255
  2. 使用服务端 websocket

解决服务器端连接数 65535 限制

  • 小于 100w
  • 大于 100w

性能评估

不同的技术栈实现,所耗费的 CPU、内存、带宽资源有所不同,参考这里

一台 AWS C3.4xlarge (16 cores, 32G memory)作为应用服务器,两台 AWS C3.2xlarge (8 cores, 16G memory) 服务器作为客户端。这两台机器作为测试客户端绰绰有余,每台客户端机器创建了十个内网虚拟 IP, 每个 IP 创建 60000 个 websocket 连,共计 120w 连接。

技术栈 仅连接内存占用 广播 20 字节(网络带宽/耗时)
Netty 18G 10M/15s
Spray 25G 46M/8s
Undertow 21G ?/15s
根据业务不同,推送形式(广播、组播、单播)的不同,内存和 CPU 又会比较大的差异。具体根据自身情况分析。

带宽预估

解决完连接数的限制后,最大连接数仅取决于服务器内存和 CPU 的硬件规格,假设硬件资源无限,服务器给客户端推送消息的瓶颈就来到了服务器的带宽大小上。带宽大小取决于推送消息的连接数、推送比、推送频率、消息大小。

假设一百万连接中有 20% 是活跃的, 每个连接每秒传输 1KB 的数据, 那么需要的网络带宽是 0.2M x 1KB/s x 8 = 1.6Gbps, 要求服务器至少是万兆网卡(10Gbps)。

实战 demo

鉴权 spring boot + websocket+ jwt+ 推送 demo

参考文档