Qt SSL:从 QML 发出的请求握手失败

Posted

技术标签:

【中文标题】Qt SSL:从 QML 发出的请求握手失败【英文标题】:Qt SSL: Handshake failed for requests made from QML 【发布时间】:2015-05-19 22:40:14 【问题描述】:

我直接从我的 qml 视图发出一些 https 请求,例如图像源。由于我有一个自签名证书服务器端,我需要告诉 qt 忽略一些 ssl 错误(我同时控制服务器和客户端应用程序,所以这应该不是问题)。

我创建了一个 QQmlNetworkAccessManagerFactory 来创建 NAM,我在其中连接到 sslErrors 信号。

UltraQmlAccessManagerFactory.h:

#ifndef FACKFACKTORy_H
#define FACKFACKTORy_H


#include <QQmlNetworkAccessManagerFactory>
#include <QObject>
#include <QNetworkReply>
#include <QList>
#include <QSslError>
#include <QNetworkAccessManager>
#include <QDebug>
#include <QSslCertificate>

class UltraQmlNetworkAccessManagerFactory : public QObject,
                                            public QQmlNetworkAccessManagerFactory 
  Q_OBJECT
private:
  QNetworkAccessManager* nam;
  QList<QSslError> expectedSslErrors;
public:
  explicit UltraQmlNetworkAccessManagerFactory();
  ~UltraQmlNetworkAccessManagerFactory();
  virtual QNetworkAccessManager* create(QObject* parent);

public slots:
  void onIgnoreSslErrors(QNetworkReply* reply, QList<QSslError> errors);
;

#endif

UltraQmlNetworkAccessManagerFactory.cpp:

#include "UltraQmlNetworkAccessManagerFactory.h"

UltraQmlNetworkAccessManagerFactory::UltraQmlNetworkAccessManagerFactory() 



UltraQmlNetworkAccessManagerFactory::~UltraQmlNetworkAccessManagerFactory() 
  delete nam;


QNetworkAccessManager* UltraQmlNetworkAccessManagerFactory::create(QObject* parent) 
  QNetworkAccessManager* nam = new QNetworkAccessManager(parent);
  QObject::connect(nam, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)),
                   this, SLOT(onIgnoreSslErrors(QNetworkReply*,QList<QSslError>))
                   );
  return nam;



void UltraQmlNetworkAccessManagerFactory::onIgnoreSslErrors(QNetworkReply *reply, QList<QSslError> errors) 
  for (int i = 0; i < errors.size(); i++) 
    qDebug() << "e: " << errors.at(i) << endl;

  
  reply->ignoreSslErrors(errors);

main.cpp 中还有一些胶水可以设置这个工厂被使用,我怀疑这部分是错误的来源,因为 qDebug 打印在输出中是可见的。

从函数/槽 onIgnoreSslErrors 中的 .cpp 文件中可以看出,我尝试忽略收到的每个错误(作为测试),但在输出中我没有得到预期的结果。

输出

e:  "The certificate is self-signed, and untrusted" 

qrc:/qml/file/ImageView.qml:16:5: QML Image: SSL handshake failed

我已经成功地从 C++ 直接使用 QSslConfiguration 创建了 QNetworkRequests,并指定了 TLSV1_0 和证书。由于我怀疑握手失败是因为一侧需要 SSL,而另一侧需要 TLS,因此我还尝试通过 QNetworkRequest 对象设置 QSslConfiguration reply-&gt;request();,但这并没有改变。

【问题讨论】:

尝试使用QSslConfiguration::setDefaultConfiguration()设置默认的SSL配置。 【参考方案1】:

(这是一个非常古老的问题,但由于我最近偶然发现并没有找到答案,我认为它仍然值得回答)

您没有显示实际设置工厂对象的位置,但它很可能是工厂对象不属于调用其create() 方法时使用的同一(实际上是任何)线程。这是Qt documentation on the class的摘录:

如果要从 create() 返回的对象的信号连接到可能在不同线程中创建的对象的插槽,则开发人员应小心

它进一步提到了authenticationRequired() 信号,但sslErrors() 的行为方式相同:这两个信号以及其他一些信号都需要直接或阻塞队列连接,以便在返回到发出位置时表示网络回复对象已经由槽配置。

在您的情况下发生的情况(很可能)如下(TL;DR:您的插槽被排队连接异步调用,因为它位于不同的线程中,而sslErrors() 需要对正在运行的网络回复进行同步更改对象;不管日志行的顺序,请求首先失败,然后调用ignoreSslErrors()):

    工厂对象被创建,QML 引擎被配置,在主线程中。 QML 引擎产生一些线程来执行后端工作,特别是对 URL 的网络请求(我在这里假设您的 ImageView.qml 有一个 Image 组件)。为了执行网络请求,这些线程调用UltraQmlNetworkAccessManagerFactory::create()create() 生成一个 NAM 对象并在其上建立连接。这里的父对象是 QQmlEngine 对象(专门用于图像请求)后端线程对象,如您所见,例如here。因此,这个 NAM 对象属于后端线程connect()默认使用Qt::AutoConnection类型,由于工厂线程和NAM对象线程不同,对应Qt::QueuedConnection。作为旁注,线程已检查at the time of signal invocation。 最终会发出QNetworkAccessManager::sslErrors() 信号。由于这是一个排队连接,唯一立即发生的事情就是在主线程的事件队列中调用onIgnoreSslErrors()。 如果您非常幸运,您可能会在此之后立即切换到主线程 - 但实际上没有什么可以确保这一点,因此控制权更有可能返回到发出 QNetworkAccessManager::sslErrors() 的站点。由于没有调用ignoreSslErrors(),因此请求握手失败。后端线程posts relevant data from the (failed) QNetworkReply object back to the main thread - see the postReply call in the middle of the method(或者它可能稍后再做——这已经不重要了)。 一旦上下文切换到主线程ignoreSslErrors() 被执行——唉,已经太晚了,因为网络回复很可能已经以失败告终;但现在第一条日志行出来了。 主线程继续通过事件循环并找到带有故障数据的QQuickPixmapReply::Event 对象。在展开一些调用和信号后,失败的图像最终出现在 QQuickImageBase::requestFinished() that prints the second log line for you 中。

至于修复,很容易将Qt::BlockingQueuedConnection 指定为connect() 的第五个参数。不幸的是,如果曾经从在主线程中运行的QQmlEngine 发出请求,并且使用该工厂创建的 NAM 实例来例如通过网络请求 QML 组件,这将死锁。到目前为止,我能想到的最好的方法是

connect(nam, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)),
        this, SLOT(onIgnoreSslErrors(QNetworkReply*,QList<QSslError>)),
        currentThread() == this->thread() ? Qt::DirectConnection
                                          : Qt::BlockingQueuedConnection
        );

【讨论】:

以上是关于Qt SSL:从 QML 发出的请求握手失败的主要内容,如果未能解决你的问题,请参考以下文章

每日一题请你分别说明一下,SSL协议和TCP协议的4次握手过程

Qt & SSL,握手失败

使用 qml 从 qt5 发布 HTTP 请求

Paypal ssl 握手失败

似乎在 Google Play 服务更新之后,使用 ION 进行 HTTPS 请求的 Android 应用程序失败

QT - WebSockets