flask-socketio 服务器和 python-socketio 之间的安全通信

Secure communication between flask-socketio server and python-socketio

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

我使用 openssl (rootCA.pem) 创建了一个证书并将其添加到 Mac 的钥匙串中。之后,我用 rootCA.pem 发布了 server.certserver.key。我使用 flask-socketio 构建了 Web 服务器,在其一侧添加了 server.certserver.key。最后,该网站与 ssl 配合得很好。

当我想保护 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 安装的信息:

This variant of Python 3.7 includes its own private copy of OpenSSL 1.1.1. The deprecated Apple-supplied OpenSSL libraries are no longer used. This means that the trust certificates in system and user keychains managed by the Keychain Access application and the security command line utility are no longer used as defaults by the Python ssl module. A sample command script is included in /Applications/Python 3.7 to install a curated bundle of default root certificates from the third-party certifi package (https://pypi.org/project/certifi/). If you choose to use certifi, you should consider subscribing to the project's email update service to be notified when the certificate bundle is updated.

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

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

在python certifi 的文件夹中,有一个名为cacert.pem 的文件。这包含根证书。所以我在cacert.pem.

的末尾添加了rootCA.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")

请帮忙!我知道我的问题很啰嗦。但我只想展示我尝试解决问题的方式。如果有任何问题,请告诉我。谢谢!

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

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

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