WebEngine Widgets Client-Zertifikat Beispiel
Ein einfaches Client-Zertifikat-Authentifizierungsszenario mit Qt WebEngine und QSslServer.
In diesem Beispiel wird ein Arbeitsablauf zur Authentifizierung von Client-Zertifikaten gezeigt. Das vorgestellte Authentifizierungsszenario kann z. B. für ein eingebettetes Gerät implementiert werden, das eine Webschnittstelle zur Verwaltung seiner Funktionen bereitstellt. Der Administrator verwendet den von Qt WebEngine betriebenen Client, um das eingebettete Gerät zu verwalten und verfügt über ein benutzerdefiniertes SSL-Zertifikat zur Authentifizierung. Die Verbindung wird mit SSL-Sockets verschlüsselt. Das eingebettete Gerät verwendet eine QSslSocket
, um die Authentifizierung und die Verschlüsselung zu handhaben. Auf diese Weise muss der Administrator keine Anmeldedaten eingeben und lediglich ein geeignetes Zertifikat auswählen, das vom Gerät erkannt wird.
In diesem Beispiel konzentrieren wir uns auf einen sehr einfachen und minimalistischen Ansatz, um den Arbeitsablauf zu demonstrieren. Beachten Sie, dass es sich bei QSslSocket um eine einfache Lösung handelt, da wir auf dem ressourcenbeschränkten eingebetteten Gerät keinen vollwertigen HTTPS-Server betreiben müssen.
Erstellen von Zertifikaten
Das Beispiel wird mit bereits generierten Zertifikaten ausgeliefert, aber sehen wir uns an, wie wir neue Zertifikate erstellen können. Wir erstellen Zertifikate für den Server und den Client mit dem OpenSSL-Tooling.
Zuerst erstellen wir die Zertifikatssignierungsanforderung CSR
und signieren sie. Wir verwenden einen privaten CA-Schlüssel, um beide lokalen Zertifikate für den Client und den Server zu signieren und auszustellen.
openssl req -out ca.pem -new -x509 -nodes -keyout ca.key
Hinweis: Geben Sie die Option -days
an, um die standardmäßige Zertifikatsgültigkeit von 30 Tagen außer Kraft zu setzen.
Lassen Sie uns nun zwei private Schlüssel für unseren Client und einen Server erstellen:
openssl genrsa -out client.key 2048
openssl genrsa -out server.key 2048
Als Nächstes benötigen wir zwei Zertifikatsignierungsanforderungen:
openssl req -key client.key -new -out client.req
openssl req -key server.key -new -out server.req
Lassen Sie uns nun beide Zertifikate aus CSRs ausstellen:
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
Der Betreff des Client-Zertifikats und die Seriennummer werden bei der Authentifizierung zur Auswahl angezeigt. Die Seriennummer kann mit ausgedruckt werden:
openssl x509 -serial -noout -in client.pem
Implementieren des Clients
Jetzt können wir unseren Webbrowser-Client implementieren.
Wir beginnen mit dem Laden unseres Zertifikats und seines privaten Schlüssels und erstellen die Instanzen QSslCertificate und QSslKey.
QFile certFile(":/resources/client.pem"); certFile.open(QIODevice::ReadOnly); const QSslCertificate cert(certFile.readAll(), QSsl::Pem); QFile keyFile(":/resources/client.key"); keyFile.open(QIODevice::ReadOnly); const QSslKey sslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "");
Nun fügen wir das Zertifikat und seinen privaten Schlüssel zu QWebEngineClientCertificateStore hinzu.
QWebEngineProfile::defaultProfile()->clientCertificateStore()->add(cert, sslKey);
Um mit Zertifikaten umzugehen, müssen wir eine Instanz von QWebEnginePage erstellen und eine Verbindung zu zwei Singals QWebEnginePage::certificateError und QWebEnginePage::selectClientCertificate herstellen. Die erste wird nur benötigt, da unser selbstsigniertes Serverzertifikat einen Zertifikatsfehler auslöst, der akzeptiert werden muss, um mit der Authentifizierung fortzufahren. In Produktionsumgebungen werden selbstsignierte Zertifikate nicht verwendet, daher behandeln wir in diesem Beispiel QWebEngineCertificateError nur, um die Bereitstellung eigener Zertifikate zu vermeiden. Beachten Sie, dass der private Schlüssel ein Geheimnis ist und niemals veröffentlicht werden sollte.
QWebEnginePage page; QObject::connect(&page, &QWebEnginePage::certificateError, [](QWebEngineCertificateError e) { e.acceptCertificate(); });
Die Handhabung für QWebEnginePage::selectClientCertificate zeigt einfach QDialog an, wobei QListWidget eine Liste von Client-Zertifikaten zur Auswahl anzeigt. Das vom Benutzer ausgewählte Zertifikat wird dann an den Aufruf von QWebEngineClientCertificateSelection::select weitergegeben.
QObject::connect( &page, &QWebEnginePage::selectClientCertificate, &page, [&cert](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(); });
Schließlich erstellen wir eine QWebEngineView für unsere QWebEnginePage, laden die Server-URL und zeigen die Seite an.
QWebEngineView view(&page); view.setUrl(QUrl("https://localhost:5555")); view.resize(800, 600); view.show();
Implementieren des Servers
Für unser eingebettetes Gerät werden wir einen minimalistischen HTTPS-Server entwickeln. Wir können QSslServer verwenden, um eingehende Verbindungen zu verarbeiten und eine Instanz von QSslSocket bereitzustellen. Dazu erstellen wir eine Instanz von QSslServer und laden, ähnlich wie bei unserer Client-Einrichtung, ein Server-Zertifikat und dessen privaten Schlüssel. Als nächstes erstellen wir die Objekte QSslCertificate und QSslKey entsprechend. Außerdem benötigen wir ein CA-Zertifikat, damit der Server das vom Client vorgelegte Zertifikat validieren kann. Die CA und das lokale Zertifikat werden auf QSslConfiguration gesetzt und später vom Server verwendet.
QSslServer server; QSslConfiguration configuration(QSslConfiguration::defaultConfiguration()); configuration.setPeerVerifyMode(QSslSocket::VerifyPeer); QFile keyFile(":/resources/server.key"); keyFile.open(QIODevice::ReadOnly); QSslKey key(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);
Als nächstes stellen wir den Server so ein, dass er auf eingehende Verbindungen an Port 5555
if (!server.listen(QHostAddress::LocalHost, 5555)) qFatal("Could not start server on localhost:5555"); sonst qInfo("Server started on localhost:5555");
Wir stellen eine Lambda-Funktion für das Signal QTcpServer::pendingConnectionAvailable zur Verfügung, in der wir die Behandlung eingehender Verbindungen implementieren. Dieses Signal wird ausgelöst, nachdem die Authentifizierung erfolgreich war und die socket
TLS-Verschlüsselung begonnen hat.
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(); });
Das oben verwendete Objekt Request
ist ein einfacher Wrapper um QByteArray, da wir QPointer verwenden, um bei der Speicherverwaltung zu helfen. Dieses Objekt sammelt die eingehenden HTTP-Daten. Es wird gelöscht, wenn die Anfrage abgeschlossen ist oder ein Socket beendet wurde.
struct Request : public QObject { QByteArray m_data; };
Die Antwort auf die Anfrage hängt von der angeforderten URL ab und wird über den Socket in Form einer HTML-Seite zurückgesendet. Bei der Root-Anfrage GET
sieht der Administrator die Nachricht Access Granted
und eine HTML-Schaltfläche Exit
. Wenn der Administrator darauf klickt, sendet der Client eine weitere Anfrage. Diesmal mit der relativen URL /exit
, die ihrerseits den Abbruch des Servers auslöst.
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(); });
Um das Beispiel auszuführen, starten Sie server
und dann client
. Nachdem Sie das Zertifikat ausgewählt haben, wird die Seite Access Granted
angezeigt.
© 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.