En esta página

Ejemplo de certificado de cliente de WebEngine Widgets

Un escenario sencillo de autenticación de certificado de cliente utilizando Qt WebEngine y QSslServer.

Ventana que muestra la lista de certificados de cliente

En este ejemplo vamos a mostrar un flujo de trabajo de autenticación de certificado de cliente. El escenario de autenticación presentado puede implementarse, por ejemplo, para un dispositivo integrado, que proporciona una interfaz web para gestionar su funcionalidad. El administrador utiliza el cliente alimentado por Qt WebEngine para mantener el dispositivo integrado y dispone de un certificado SSL personalizado para autenticarse. La conexión se cifra con sockets SSL. El dispositivo integrado utiliza QSslSocket para gestionar la autenticación y el cifrado. De este modo, el administrador no tiene que introducir ninguna credencial y sólo necesita seleccionar un certificado adecuado que sea reconocido por el dispositivo.

En el ejemplo nos centramos en un enfoque muy simple y minimalista para demostrar el flujo de trabajo. Tenga en cuenta que QSslSocket es una solución de bajo nivel, ya que no tenemos que ejecutar un servidor HTTPS completo en el dispositivo integrado de recursos limitados.

Creación de certificados

El ejemplo viene con certificados ya generados, pero veamos cómo generar nuevos. Creamos certificados para el servidor y el cliente utilizando la herramienta OpenSSL.

Primero, creamos la solicitud de firma de certificado CSR y la firmamos. Utilizaremos una clave privada de CA para firmar y emitir ambos certificados locales para el cliente y el servidor.

openssl req -out ca.pem -new -x509 -nodes -keyout ca.key

Nota: Especifique la opción -days para anular la validez predeterminada del certificado de 30 días.

Ahora, vamos a crear dos claves privadas para nuestro cliente y un servidor:

openssl genrsa -out client.key 2048
openssl genrsa -out server.key 2048

A continuación necesitamos dos solicitudes de firma de certificado:

openssl req -key client.key -new -out client.req
openssl req -key server.key -new -out server.req

Emitamos ahora ambos certificados a partir de CSRs:

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

El asunto del certificado del cliente y el número de serie se mostrarán para su selección durante la autenticación. El número de serie se puede imprimir con:

openssl x509 -serial -noout -in client.pem

Implementación del cliente

Ahora podemos implementar nuestro cliente de navegador web.

Empezaremos cargando nuestro certificado y su clave privada y creando las instancias QSslCertificate y QSslKey.

    QFile certFile(":/recursos/cliente.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(":/recursos/cliente.key"); if (!keyFile.open(QIODevice::Sólo lectura)) {        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, "");

Ahora añadimos el certificado y su clave privada a QWebEngineClientCertificateStore.

    QWebEngineProfile::defaultProfile()->clientCertificateStore()->add(cert, sslKey);

Para manejar certificados necesitamos crear una instancia de QWebEnginePage y conectarnos a dos singals QWebEnginePage::certificateError y QWebEnginePage::selectClientCertificate. El primero sólo es necesario ya que nuestro certificado de servidor autofirmado provocará un error de certificado, que tiene que ser aceptado para proceder con la autenticación. En entornos de producción no se utilizan certificados autofirmados, por lo tanto en este ejemplo manejamos QWebEngineCertificateError sólo para evitar proporcionar certificados propios. Tenga en cuenta que la clave privada es un secreto y nunca debe ser publicada.

    QWebEnginePage page;
    QObject::connect(&page, &QWebEnginePage::certificateError,
                     [](QWebEngineCertificateError e) { e.acceptCertificate(); });

El manejo para QWebEnginePage::selectClientCertificate simplemente muestra QDialog con QListWidget mostrando una lista de certificados de cliente para elegir. El certificado seleccionado por el usuario se pasa a la llamada 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();
            });

Finalmente, creamos un QWebEngineView para nuestro QWebEnginePage, cargamos la URL del servidor, y mostramos la página.

    QWebEngineView view(&page);
    view.setUrl(QUrl("https://localhost:5555"));
    view.resize(800, 600);
    view.show();

Implementación del servidor

Para nuestro dispositivo embebido desarrollaremos un servidor HTTPS minimalista. Podemos utilizar QSslServer para gestionar las conexiones entrantes y proporcionar una instancia de QSslSocket. Para ello, creamos una instancia de QSslServer y, de forma similar a la configuración de nuestro cliente, cargamos un certificado de servidor y su clave privada. A continuación, creamos los objetos QSslCertificate y QSslKey en consecuencia. Además, necesitamos un certificado CA para que el servidor pueda validar el certificado presentado por el cliente. La CA y el certificado local se establecen en QSslConfiguration y son utilizados posteriormente por el servidor.

    QSslServer servidor;    QSslConfiguration configuración(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(":/recursos/servidor.pem"); configuration.setLocalCertificateChain(localCerts);    QList<QSslCertificate> caCerts = QSslCertificate::fromPath(":resources/ca.pem"); configuration.addCaCertificates(caCerts); server.setSslConfiguration(configuration);

A continuación, configuramos el servidor para que escuche las conexiones entrantes en el puerto 5555

   if (!server.listen(QHostAddress::LocalHost, 5555))        qFatal("Could not start server on localhost:5555");
   si no        qInfo("Server started on localhost:5555");

Proporcionamos una función lambda para la señal QTcpServer::pendingConnectionAvailable, donde implementamos la gestión de las conexiones entrantes. Esta señal se activa después de que la autenticación haya tenido éxito y el cifrado TLS de socket haya comenzado.

    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();
                         });

El objeto Request utilizado anteriormente es una simple envoltura alrededor de QByteArray ya que utilizamos QPointer para ayudar con la gestión de memoria. Este objeto recoge los datos HTTP entrantes. Se elimina cuando la petición ha finalizado o se ha terminado un socket.

struct Request : public QObject
{
    QByteArray m_data;
};

La respuesta a la petición depende de la URL solicitada, y se devuelve a través del socket en forma de página HTML. Para la solicitud raíz GET el administrador ve el mensaje Access Granted y un botón HTML Exit. Si el administrador lo pulsa, el cliente envía otra petición. Esta vez con la URL relativa /exit, que a su vez provoca la terminación del servidor.

        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();
        });

Para ejecutar el ejemplo, inicie server y, a continuación, client. Tras seleccionar el certificado, se muestra la página Access Granted.

Página que muestra el mensaje Acceso concedido

Proyecto de ejemplo @ code.qt.io

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