in Architecture

WebSocket 的鉴权授权方案

引子

WebSocket 是个好东西,为我们提供了便捷且实时的通讯能力。然而,对于 WebSocket 客户端的鉴权,协议的 RFC 是这么说的:

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 是独立的、创建在 TCP 上的协议。

为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”。

实现步骤:

1. 发起请求的浏览器端,发出协商报文:

2. 服务器端响应101状态码(即切换到socket通讯方式),其报文:

3. 协议切换完成,双方使用Socket通讯

直观的协商及通讯过程:

websocket-lifecycle

方案

通过对协议实现的解读可知:在 HTTP 切换到 Socket 之前,没有什么好的机会进行鉴权,因为在这个时间节点,报文(或者说请求的Headers)必须遵守协议规范。但这不妨碍我们在协议切换完成后,进行鉴权授权:

鉴权

  1. 在连接建立时,检查连接的HTTP请求头信息(比如cookies中关于用户的身份信息)
  2. 在每次接收到消息时,检查连接是否已授权过,及授权是否过期
  3. 以上两点,只要答案为否,则服务端主动关闭socket连接

授权

服务端在连接建立时,颁发一个ticket给peer端,这个ticket可以包含但不限于:

  • peer端的uniqueId(可以是ip,userid,deviceid…任一种具备唯一性的键)
  • 过期时间的timestamp
  • token:由以上信息生成的哈希值,最好能加盐

安全性的补充说明

有朋友问:这一套机制如何防范重放攻击,私以为可以从以下几点出发:

  • 可以用这里提到的expires,保证过期,如果你愿意,甚至可以每次下发消息时都发送一个新的Ticket,只要上传消息对不上这个Ticket,就断开,这样非Original Peer是没法重放的
  • 可以结合redis,实现 ratelimit,防止高频刷接口,这个可以参考 express-rate-limit,原理很简单,不展开
  • 为防止中间人,最好使用wss(TLS)

代码实现

WebSocket连接处理,基于 node.js 的 ws 实现:

授权用到的 Ticket(这里存储用到的是knex + postgreSQL):

utils 的哈希方法:

引用

  1. https://devcenter.heroku.com/articles/websocket-security
  2. https://tools.ietf.org/html/rfc6455
  3. https://zh.wikipedia.org/wiki/WebSocket
打赏作者
您的支持将激励我继续创作!

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

Write a Comment

Comment

  1. 文章很好, 但是我有些问题:

    连接之后鉴权存在占用 fd 和额外的服务器开销,这样攻击者拿到你的服务器地址后不断的发起连接攻击。

    我之前做im是也遇到了问题,解决方案也是和你文章提到的一样。我一直在想能不能在kehuUpgrade: websocket

    • 我之前做im是也遇到了问题,解决方案也是和你文章提到的一样。我一直在想能不能在客户端 Upgrade: websocket 时鉴权

  2. 博主您好:
    您给的方案描述“协议切换完成后,进行鉴权授权”,也就是使用WebSocket进行鉴权授权。而这似乎与第一个参考链接中的Authentication/authorization的内容不符,链接内容中提到客户端使用HTTP协议先获得一个ticket,然后再用这个ticket建立WebSocket连接。不知我所理解的是否有误,还请博主解答。