WebEngine 小工具客户端证书示例
使用Qt WebEngine 和QSslServer 的简单客户端证书验证方案。
在本示例中,我们将展示客户证书验证工作流程。所展示的验证方案可用于嵌入式设备,该设备提供一个网络接口来处理其功能。管理员使用Qt WebEngine 供电客户端维护嵌入式设备,并使用自定义 SSL 证书进行身份验证。连接使用 SSL 套接字加密。嵌入式设备使用QSslSocket
来处理身份验证和加密。这样,管理员就无需输入任何凭证,只需选择设备可识别的适当证书即可。
在示例中,我们将以非常简单和简约的方式来演示工作流程。请注意,QSslSocket 是一种低级解决方案,因为我们不必在资源有限的嵌入式设备上运行完整的 HTTPS 服务器。
创建证书
示例中已经生成了证书,让我们看看如何生成新证书。我们使用OpenSSL 工具为服务器和客户端创建证书。
首先,我们创建证书签名请求CSR
并签名。我们将使用 CA 私钥签署和签发客户端和服务器的本地证书。
openssl req -out ca.pem -new -x509 -nodes -keyout ca.key
注意: 指定-days
选项可覆盖默认的 30 天证书有效期。
现在,让我们为客户端和服务器创建两个私钥:
openssl genrsa -out client.key 2048
openssl genrsa -out server.key 2048
接下来,我们需要两个证书签名请求:
openssl req -key client.key -new -out client.req
openssl req -key server.key -new -out server.req
现在让我们用 CSR 签发这两份证书:
openssl x509 -req -in client.req -out client.pem -CA ca.pem -CAkey ca.key
openssl x509 -req -in server.req -out server.pem -CA ca.pem -CAkey ca.key
客户端证书主题和序列号将在验证时显示出来,供用户选择。序列号可以通过以下方式打印
openssl x509 -serial -noout -in client.pem
执行客户端
现在我们可以实现网络浏览器客户端了。
我们首先加载证书及其私钥,并创建QSslCertificate 和QSslKey 实例。
QFilecertFile(":/resources/client.pem");如果(!certFile.open(QIODevice::ReadOnly)) { qFatal("Failed to read cert file %s: %s", qPrintable(certFile.fileName()), qPrintable(certFile.errorString())); }constQSslCertificatecert(certFile.readAll()、 QSsl::Pem); QFilekeyFile(":/resources/client.key");if(!keyFile.open(QIODevice::ReadOnly)) { qFatal("Failed to read key file %s: %s", qPrintable(keyFile.fileName()), qPrintable(keyFile.errorString())); }constQSslKeysslKey(keyFile.readAll())、QSsl::Rsa、 QSsl::Pem、 QSsl::私钥, "");
现在,我们将证书及其私钥添加到QWebEngineClientCertificateStore 。
QWebEngineProfile::defaultProfile()->clientCertificateStore()->add(cert, sslKey);
为了处理证书,我们需要创建QWebEnginePage 的实例,并连接到两个单例QWebEnginePage::certificateError 和QWebEnginePage::selectClientCertificate 。由于自签名服务器证书会引发证书错误,因此只需要第一个证书,但必须接受该错误才能继续进行身份验证。在生产环境中不使用自签名证书,因此在本例中,我们处理QWebEngineCertificateError 只是为了避免提供正确的证书。请注意,私钥是一个秘密,绝对不能公开。
QWebEnginePage page; QObject::connect(&page, &QWebEnginePage::certificateError, [](QWebEngineCertificateError e) { e.acceptCertificate(); });
对QWebEnginePage::selectClientCertificate 的处理只需显示QDialog ,QListWidget ,显示可供选择的客户端证书列表。然后将用户选择的证书传递给QWebEngineClientCertificateSelection::select 调用。
QObject::connect( &page, &QWebEnginePage::selectClientCertificate, &page, [](QWebEngineClientCertificateSelection selection) { QDialog dialog; QVBoxLayout *layout = new QVBoxLayout; QLabel *label = new QLabel(QLatin1String("Select certificate")); QListWidget *listWidget = new QListWidget; listWidget->setSelectionMode(QAbstractItemView::SingleSelection); QPushButton *button = new QPushButton(QLatin1String("Select")); layout->addWidget(label); layout->addWidget(listWidget); layout->addWidget(button); QObject::connect(button, &QPushButton::clicked, [&dialog]() { dialog.accept(); }); const QList<QSslCertificate> &list = selection.certificates(); for (const QSslCertificate &cert : list) { listWidget->addItem(cert.subjectDisplayName() + " : " + cert.serialNumber()); } dialog.setLayout(layout); if (dialog.exec() == QDialog::Accepted) selection.select(list[listWidget->currentRow()]); else selection.selectNone(); });
最后,我们为QWebEnginePage 创建QWebEngineView ,加载服务器 URL 并显示页面。
QWebEngineView view(&page); view.setUrl(QUrl("https://localhost:5555")); view.resize(800, 600); view.show();
实现服务器
我们将为嵌入式设备开发一个简约的 HTTPS 服务器。我们可以使用QSslServer 处理传入连接,并提供QSslSocket 实例。为此,我们将创建QSslServer 实例,并与客户端设置类似,加载服务器证书及其私钥。接下来,我们相应地创建QSslCertificate 和QSslKey 对象。此外,我们还需要 CA 证书,这样服务器才能验证客户端提交的证书。CA 和本地证书被设置为QSslConfiguration ,稍后由服务器使用。
QSslServer服务器; QSslConfiguration配置QSslConfiguration::defaultConfiguration()); configuration.setPeerVerifyMode(QSslSocket::VerifyPeer); QFilekeyFile(":/resources/server.key");if(!keyFile.open(QIODevice::ReadOnly)) { qFatal("Failed to read key file %s: %s", qPrintable(keyFile.fileName()), qPrintable(keyFile.errorString())); } QSslKeykey(keyFile.readAll()、 QSsl::Rsa、 QSsl::Pem、 QSsl::PrivateKey); configuration.setPrivateKey(key); QList<QSslCertificate>localCerts=QSslCertificate::fromPath(":/resources/server.pem"); configuration.setLocalCertificateChain(localCerts); QList<QSslCertificate>caCerts=QSslCertificate::fromPath(":resources/ca.pem"); configuration.addCaCertificates(caCerts); server.setSslConfiguration(configuration);
接下来,我们将服务器设置为在端口上侦听传入连接5555
如果(!server.listen(QHostAddress::LocalHost, 5555)) qFatal("Could not start server on localhost:5555"); 不然 qInfo("Server started on localhost:5555");
我们为QTcpServer::pendingConnectionAvailable 信号提供了一个 lambda 函数,用于处理传入连接。该信号在身份验证成功且socket
TLS 加密开始后触发。
QObject::connect(&server, &QTcpServer::pendingConnectionAvailable, [&server]() { QTcpSocket *socket = server.nextPendingConnection(); Q_ASSERT(socket); QPointer<Request> request(new Request); QObject::connect(socket, &QAbstractSocket::disconnected, socket, [socket, request]() mutable { delete request; socket->deleteLater(); });
上面使用的Request
对象是QByteArray 的一个简单封装,因为我们使用QPointer 来帮助进行内存管理。该对象收集传入的 HTTP 数据。当请求完成或套接字终止时,它就会被删除。
struct Request : public QObject { QByteArray m_data; };
请求的回复取决于所请求的 URL,并通过套接字以 HTML 页面的形式发送回来。对于GET
根请求,管理员会看到Access Granted
消息和一个Exit
HTML 按钮。如果管理员点击该按钮,客户端就会发送另一个请求。这一次发送的是/exit
相对 URL,从而触发服务器终止。
QObject::connect(socket, &QTcpSocket::readyRead, socket, [socket, request]() mutable { request->m_data.append(socket->readAll()); if (!request->m_data.endsWith("\r\n\r\n")) return; socket->write(http_ok); socket->write(html_start); if (request->m_data.startsWith("GET / ")) { socket->write("<p>ACCESS GRANTED !</p>"); socket->write("<p>You reached the place, where no one has gone before.</p>"); socket->write("<button onclick=\"window.location.href='/exit'\">Exit</button>"); } else if (request->m_data.startsWith("GET /exit ")) { socket->write("<p>BYE !</p>"); socket->write("<p>Have good day ...</p>"); QTimer::singleShot(0, &QCoreApplication::quit); } else { socket->write("<p>There is nothing to see here.</p>"); } socket->write(html_end); delete request; socket->disconnectFromHost(); });
要运行示例,请启动server
,然后启动client
。选择证书后,将显示Access Granted
页面。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.