Exemple de certificat client pour les widgets WebEngine
Un scénario simple d'authentification par certificat client utilisant Qt WebEngine et QSslServer.

Dans cet exemple, nous allons montrer un flux de travail d'authentification de certificat client. Le scénario d'authentification présenté peut par exemple être mis en œuvre pour un appareil embarqué, qui fournit une interface web pour gérer ses fonctionnalités. L'administrateur utilise le client alimenté par Qt WebEngine pour maintenir l'appareil embarqué et dispose d'un certificat SSL personnalisé pour s'authentifier. La connexion est cryptée avec des sockets SSL. L'appareil intégré utilise une adresse QSslSocket pour gérer l'authentification et le cryptage. De cette manière, l'administrateur n'a pas besoin de saisir d'informations d'identification et doit simplement sélectionner un certificat approprié qui est reconnu par l'appareil.
Dans l'exemple, nous nous concentrons sur une approche très simple et minimaliste pour démontrer le flux de travail. Notez que QSslSocket est une solution de bas niveau car nous n'avons pas besoin d'exécuter un serveur HTTPS complet sur l'appareil embarqué dont les ressources sont limitées.
Création de certificats
L'exemple est fourni avec des certificats déjà générés, mais voyons comment en générer de nouveaux. Nous créons des certificats pour le serveur et le client en utilisant l'outil OpenSSL.
Tout d'abord, nous créons la demande de signature de certificat CSR et la signons. Nous utiliserons la clé privée d'une autorité de certification pour signer et émettre les deux certificats locaux pour le client et le serveur.
openssl req -out ca.pem -new -x509 -nodes -keyout ca.key
Remarque : l'option -days permet d'ignorer la validité par défaut des certificats, qui est de 30 jours.
Créons maintenant deux clés privées pour notre client et un serveur :
openssl genrsa -out client.key 2048
openssl genrsa -out server.key 2048
Ensuite, nous avons besoin de deux demandes de signature de certificat :
openssl req -key client.key -new -out client.req
openssl req -key server.key -new -out server.req
Emettons maintenant les deux certificats à partir des 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
Le sujet du certificat du client et le numéro de série seront affichés pour être sélectionnés lors de l'authentification. Le numéro de série peut être imprimé avec :
openssl x509 -serial -noout -in client.pem
Mise en œuvre du client
Nous pouvons maintenant implémenter notre navigateur web client.
Nous commençons par charger notre certificat et sa clé privée et par créer les instances QSslCertificate et QSslKey.
QFile certFile(":/resources/client.pem") ; if (!certFile.open(QIODevice::ReadOnly)) { qFatal("Failed to read cert file %s: %s", qPrintable(certFile.fileName()), qPrintable(certFile.errorString())); } const QSslCertificate cert(certFile.readAll(), QSsl::Pem) ; QFile keyFile(":/resources/client.key") ; if (!keyFile.open(QIODevice::ReadOnly)) { qFatal("Failed to read key file %s: %s", qPrintable(keyFile.fileName()), qPrintable(keyFile.errorString())); } const QSslKey sslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "") ;
Nous ajoutons maintenant le certificat et sa clé privée à QWebEngineClientCertificateStore.
QWebEngineProfile::defaultProfile()->clientCertificateStore()->add(cert, sslKey);
Pour gérer les certificats, nous devons créer une instance de QWebEnginePage et nous connecter à deux singals QWebEnginePage::certificateError et QWebEnginePage::selectClientCertificate. Le premier n'est nécessaire que parce que notre certificat de serveur auto-signé déclenchera une erreur de certificat, qui doit être acceptée pour procéder à l'authentification. Dans les environnements de production, les certificats auto-signés ne sont pas utilisés, c'est pourquoi, dans cet exemple, nous traitons QWebEngineCertificateError simplement pour éviter de fournir des certificats appropriés. Notez que la clé privée est un secret et ne doit jamais être publiée.
QWebEnginePage page; QObject::connect(&page, &QWebEnginePage::certificateError, [](QWebEngineCertificateError e) { e.acceptCertificate(); });
La gestion de QWebEnginePage::selectClientCertificate affiche simplement QDialog avec QListWidget montrant une liste de certificats clients à choisir. Le certificat choisi par l'utilisateur est ensuite transmis à l'appel 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(); });
Enfin, nous créons un QWebEngineView pour notre QWebEnginePage, nous chargeons l'URL du serveur et nous affichons la page.
QWebEngineView view(&page); view.setUrl(QUrl("https://localhost:5555")); view.resize(800, 600); view.show();
Mise en œuvre du serveur
Pour notre appareil embarqué, nous allons développer un serveur HTTPS minimaliste. Nous pouvons utiliser QSslServer pour gérer les connexions entrantes et fournir une instance de QSslSocket. Pour ce faire, nous créons une instance de QSslServer et, de la même manière que pour notre configuration client, nous chargeons un certificat de serveur et sa clé privée. Ensuite, nous créons les objets QSslCertificate et QSslKey en conséquence. En outre, nous avons besoin d'un certificat d'autorité de certification pour que le serveur puisse valider le certificat présenté par le client. L'autorité de certification et le certificat local sont définis sur QSslConfiguration et utilisés ultérieurement par le serveur.
QSslServer serveur ; QSslConfiguration configuration(QSslConfiguration::defaultConfiguration()) ; configuration.setPeerVerifyMode(QSslSocket::VerifyPeer) ; QFile keyFile(":/resources/server.key") ; if (!keyFile.open(QIODevice::ReadOnly)) { qFatal("Failed to read key file %s: %s", qPrintable(keyFile.fileName()), qPrintable(keyFile.errorString())); } 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) ;
Ensuite, nous configurons le serveur pour qu'il écoute les connexions entrantes sur le port 5555
if (!server.listen(QHostAddress::LocalHost, 5555)) qFatal("Could not start server on localhost:5555"); autre qInfo("Server started on localhost:5555");
Nous fournissons une fonction lambda pour le signal QTcpServer::pendingConnectionAvailable, dans laquelle nous implémentons la gestion des connexions entrantes. Ce signal est déclenché une fois que l'authentification a réussi et que le cryptage TLS de socket a commencé.
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(); });
L'objet Request utilisé ci-dessus est une simple enveloppe autour de QByteArray, car nous utilisons QPointer pour faciliter la gestion de la mémoire. Cet objet recueille les données HTTP entrantes. Il est supprimé lorsque la requête est terminée ou qu'une socket a été fermée.
struct Request : public QObject { QByteArray m_data; };
La réponse à la requête dépend de l'URL demandée et est renvoyée par la socket sous la forme d'une page HTML. Pour la requête GET root, l'administrateur voit le message Access Granted et un bouton HTML Exit. Si l'administrateur clique sur ce bouton, le client envoie une autre requête. Cette fois avec l'URL relative /exit, qui déclenche à son tour l'arrêt du serveur.
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(); });
Pour exécuter l'exemple, lancez server puis client. Après avoir sélectionné le certificat, la page Access Granted s'affiche.

© 2026 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.