首页 > 解决方案 > flask-socketio 服务器和 python-socketio 之间的安全通信

问题描述

我正在使用 flask-socketio 和 python-socketio 开发一个项目。为了保证通信安全,系统需要升级 SSL。我正在使用 Mac 和 Python 3.7。

我使用 openssl ( ) 创建了一个证书rootCA.pem并将其添加到 Mac 的钥匙串中。之后,我发布server.cert了. 我使用flask-socketio构建了Web服务器,并添加了它。最后,该网站使用 ssl 运行良好。server.keyrootCA.pemserver.certserver.key

当我想保护flask-socketio服务器和python-socketio客户端之间的通信时,问题就发生了。当客户端尝试与服务器连接时,连接被拒绝并引发unknown ca error

ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:2488)

我做了一些研究并意识到这个问题可能是由于 Python 3.7 使用了它自己的 OpenSSL 私有副本。因此,SocketIO 客户端无法验证我的自签名证书。这是来自 Python 3.7 安装的信息:

Python 3.7 的这个变体包括它自己的 OpenSSL 1.1.1 私有副本。不再使用已弃用的 Apple 提供的 OpenSSL 库。 这意味着由 Keychain Access 应用程序和安全命令行实用程序管理的系统和用户钥匙串中的信任证书不再被 Python ssl 模块用作默认值。 /Applications/Python 3.7 中包含一个示例命令脚本,用于安装来自第三方 certifi 包 ( https://pypi.org/project/certifi/ ) 的默认根证书的精选捆绑包。如果您选择使用 certifi,则应考虑订阅项目的电子邮件更新服务,以便在更新证书包时收到通知。

所以我去了文件夹/Applications/Python 3.7并执行了Install Certificates.command.

然而,这并不是问题的关键。因为 python certifi 是一个精心策划的 Root Certificates 集合。当然,它不包含我的自签名证书。所以,要解决这个问题,我需要让 Python 找到rootCA.pem.

在 python certifi 的文件夹中,有一个文件名为cacert.pem. 这包含根证书。所以我rootCA.pem在末尾添加了 的内容cacert.pem

之后,我在命令行中尝试了这段代码:

openssl verify -CAfile cacert.pem server.crt

和输出:

server.crt: OK

我认为,在这种情况下,cacert.pem可以验证服务器的证书。我知道这不是一个优雅的解决方案。如果您有更好的方法让 python 找到我的自签名证书,请告诉我。

之后,我尝试使用 python-socketio 连接到 flask-socketio 服务器。这一次,我又犯了一个错误。在服务器端,日志信息为:

(57183) accepted ('127.0.0.1', 56322)
0fd838477c534dfda802bfb0d130d358: Sending packet OPEN data {'sid': '0fd838477c534dfda802bfb0d130d358', 'upgrades': ['websocket'], 'pingTimeout': 60000, 'pingInterval': 25000}
0fd838477c534dfda802bfb0d130d358: Sending packet MESSAGE data 0
127.0.0.1 - - [10/Oct/2019 08:50:36] "GET /socket.io/?transport=polling&EIO=3&t=1570706436.665149 HTTP/1.1" 200 349 0.000436
(57183) accepted ('127.0.0.1', 56324)
http://localhost:3000 is not an accepted origin.
127.0.0.1 - - [10/Oct/2019 08:50:36] "GET /socket.io/?transport=websocket&EIO=3&sid=0fd838477c534dfda802bfb0d130d358&t=1570706436.685631 HTTP/1.1" 400 122 0.000182

在客户端,错误是这样抛出的:

Traceback (most recent call last):
  File "simple_client.py", line 18, in <module>
    sio.connect('https://localhost:3000', namespaces="/channel_A")
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/socketio/client.py", line 262, in connect
    engineio_path=socketio_path)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/engineio/client.py", line 170, in connect
    url, headers, engineio_path)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/engineio/client.py", line 308, in _connect_polling
    if self._connect_websocket(url, headers, engineio_path):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/engineio/client.py", line 346, in _connect_websocket
    cookie=cookies)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_core.py", line 514, in create_connection
    websock.connect(url, **options)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_core.py", line 226, in connect
    self.handshake_response = handshake(self.sock, *addrs, **options)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_handshake.py", line 79, in handshake
    status, resp = _get_resp_headers(sock)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_handshake.py", line 160, in _get_resp_headers
    raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers)
websocket._exceptions.WebSocketBadStatusException: Handshake status 400 BAD REQUEST

我不知道为什么会这样。flask-socketio 服务器和 python-socketio 客户端之间的通信在没有 SSL 的情况下运行良好。所以我认为代码可能没有什么问题,但我还是在这里给出代码。这是针对服务器的:

from flask import Flask, render_template
from flask_socketio import SocketIO
from flask_socketio import Namespace
import eventlet

app = Flask(__name__, template_folder="templates")

app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, engineio_logger=True, logger=True)

# Create a URL route in our application for "/"
@app.route('/')
def home():
    """
    This function loads the homepage
    """
    return render_template('index.html')


class MyCustomNamespace(Namespace):
    def on_connect(self):
        print("Client just connected")

    def on_disconnect(self):
        print("Client just left")

    def on_messages(self, data):                     
        print(f"\nReceived data from client: \n {data}\n")
        return data

socketio.on_namespace(MyCustomNamespace('/channel_A'))

if __name__ == "__main__":
    eventlet.wsgi.server(
        eventlet.wrap_ssl(eventlet.listen(("localhost", 3000)),
                          certfile='server.crt',
                          keyfile='server.key',
                          server_side=True), app)   
    # socketio.run(app, host="localhost", port=3000, debug=True) 

这是给客户的:

import socketio

sio = socketio.Client()

def message_received(data):
    print(f"Message {data} received")

@sio.on('connect', namespace="/channel_A")
def on_connect():
    print("Connect...")

@sio.on('disconnect', namespace="/channel_A")
def on_disconnect():
    print(f"Disconnected from server")

sio.connect('https://localhost:3000', namespaces="/channel_A")

请帮忙!我知道我的问题很冗长。但我只想展示我试图解决问题的方式。如果有什么问题,请告诉我。谢谢!

标签: pythonsslwebsocketflask-socketiopython-socketio

解决方案


最近版本的 Flask-SocketIO 配置了关于跨域设置的最安全设置,即只允许同源。如果您的前端应用程序和 Flask 应用程序运行在不同的服务器或端口上,那么您必须配置跨域以便它们可以一起工作。

例如,如果您的前端托管在http://localhost:3000,您可以允许它作为来源:

socketio = SocketIO(app, cors_allowed_origins='http://localhost:3000')

推荐阅读