如何在 Django 中实现 WebSocket 连接的安全性

在现代网页中,WebSocket连接扮演着越来越重要的角色,尤其是在需要即时通讯的场景中。本文将分享如何在 Django 中实现 WebSocket 连接的安全性。

作者:Natlee

项目地址:https://github.com/NatLee/telepy

最近,我有一个side project Telepy 使用到了web terminal,而这个terminal是基于WebSocket连接的

在这个过程中,我发现了一个问题:WebSocket连接的安全性。

第一次踩坑

一开始,我只是简单地使用了Django Channels来实现WebSocket连接。

但是这样做有一个安全隐忧:连接的建立过程中,没有任何认证机制!

这意味着任何人都可以通过WebSocket连接到我的server,这是非常危险的!

于是我开始思考该如何在WebSocket连接中实现认证。

然后我把目光转向了URL Query String,我很快就把这个功能实现出来,但这个方法有一个致命的缺点!

URL Query String是明文的!

如何在 Django 中实现 WebSocket 连接的安全性

第二次踩坑

有了第一次的经验,我又思考了一下。

如果URL不行的话,是不是有方法能够像HTTP一样传递Header?

于是我找到了WebSocket Subprotocol。

什么是Subprotocol?

Subprotocol是WebSocket的一个栏位,它允许使用者和伺服器在建立连接时传递一些参数,这些参数通常是拿来告诉伺服器接下来的传输要使用什么协议。

例如,我可以跟伺服器说:我要使用JSON协议来传输资料,这样伺服器就会知道要JSON用来解析资料

这个功能在WebSocket连接中非常有用,而且也很安全!

因为Subprotocol在建立连接时就会被传递,这个传递过程会被WSS保护,所以不用担心中间人攻击

但违背这个功能的初衷,我们可以利用Subprotocol来传递一些认证资讯。

用力更新一波

这次在专案中的更新主要集中在TerminalConsumer

里面引入了WebSocket Subprotocol的使用,这个改变主要影响了连接的建立过程,特别是在认证和参数传递方面。

引入Subprotocol

更新后的程式码码现在会检查WebSocket连接请求中的Subprotocol:

if self.scope['subprotocols']:
     for protocol in self.scope['subprotocols']:
          if protocol.startswith('token.'):
                base64_encoded_token = protocol.split('.', 1)[1]
                token = base64.b64decode(base64_encoded_token).decode()
          elif protocol.startswith('server.'):
                server_id = protocol.split('.', 1)[1]
          elif protocol.startswith('username.'):
                username = protocol.split('.', 1)[1]
          elif protocol.startswith('auth.'):
                subprotocol_auth = protocol

可以注意到,使用Django Channels的self.scope['subprotocols']可以获取到Subprotocol的资讯

参数检查

在更新后的程式码中,我们会检查Subprotocol中的参数是否合法:

 if  not token or  not server_id or  not username or  not subprotocol_auth: 
      logger. error ( "Missing required subprotocols" ) 
      await self . close (code= 4000 ) 
      return

如果参数不合法,连接会被强制关闭。

接受连接

在检查完参数后,我们就可以在后端接受连接了:

await self.accept(subprotocol=subprotocol_auth)

这边的subprotocol_auth就是我们在Subprotocol中传递的认证资讯。

如果我们没有在self.accept()中传入subprotocol的话,所有的WS连接都会被后端拒绝。

因为对于subprotocol来说,这本来就是跟后端沟通用的协议。

前端的更新

在前端,这是原先的WebSocket连接的建立过程:

const ws = new  WebSocket ( ` ${ws_scheme} :// ${ window .location.host} /ws/terminal/?token= ${token} &server_id= ${server_id} &username= ${username} ` );

这样的连接方式是不安全的

因为token会被暴露在URL中!

因此,我们需要将这些资讯放到Subprotocol中:

// 连接不再使用URL Query String 
const ws_path = ` ${ws_scheme} :// ${ window .location.host} /ws/terminal/` ; 

// 将需要的资讯放到Subprotocol中
const tokenInfo = `token . ${btoa(accessToken)} ` ; 
const serverInfo = `server. ${serverID} ` ; 
const usernameInfo = `username. ${username} ` ; 
// 这边的ticket是一个自定义的token,用来做subprotocol的协议名称
// 使用SHA256去避免特殊符号,例如`=` 
let ticket = sha256 ( ` ${serverID} . ${username} ` ); 
ticket = `auth. ${ticket} ` ; 
const socket = new  WebSocket (ws_path, [tokenInfo, serverInfo, usernameInfo, ticket]);

如此一来,就解决了URL Query String的问题,也增加了连接的安全性

前端会将这些资讯放到Subprotocol中的sec-websocket-protocol,然后在连接时传递给后端

连接完成后,后端会选择协议跟前端使用Websocket沟通!

这边再次提醒! Subprotocol中不能有特殊字元(例如转base64常常结尾出现的=符号),否则会导致连接失败!

在开发者工具中,可以看到Subprotocol连接的资讯:

如何在 Django 中实现 WebSocket 连接的安全性

结语

这次在side project的更新学习了如何使用Subprotocol来增加WebSocket连接的安全性。

这个方法非常适合在需要认证的场景中使用,并且也很容易实现。

要注意的地方是,Subprotocol的使用需要前后端的配合,因此在更新时要注意前后端的一致性。

而且栏位的长度也有限制,因此在使用时要注意栏位的长度!

不能直接把整个JWT塞进去,这样会导致Subprotocol的长度过长。

参考资料

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论