笔记

  在QT网络编程中,很多请求都是以HTTP,通过POST/GET方式来完成,简单高效。但是HTTP存在很严重的安全问题,HTTP数据是以明文方式传输,可以很容易被别人监听、抓取到。所以...HTTPS就应运而生了,它的存在就是为了解决HTTP方式传输的不安全问题。本篇文章总结了一个HTTPS加密传输的例子,很有参考意义,谨以此笔记记录下来。

PS: 首先要说明的是,QT是默认使用OpenSSL的。如果JAVA服务器上使用自带JDK生成的自签证书,在低版本的QT中是需要做一步转化工作的。需要将clent.p12文件,转成cert.pem 证书文件和key.pem密钥文件,将tomcat.keystore信任库文件转成tomcat.pem文件,然后再加载这三个文件,才能进行通信,这里坑了我许久。

在QT 5.1x 版本之后,是可以直接加载JDK 生成的自签证书clent.p12 文件的,本文是以直接加载clent.p12为例,讲解如何使用QT客户端与JAVA服务器进行通信。准备工作:将ssleay32.dll libeay32.dll运行库和client.p12证书文件与.exe运行文件放在同一目录下。

设置证书、网络配置及发送请求:


//声明一个 QNetworkAccessManager 对象
QNetworkAccessManager *m_accessManager_Registered;

m_accessManager_Registered = new QNetworkAccessManager(this);    //实例化一个对象

//设置连接槽
QObject::connect(m_accessManager_Registered, SIGNAL(finished(QNetworkReply*)), this, SLOT(finishedSlot_Registered(QNetworkReply*)));

//用户登陆验证;
void Widget::on_loginBt_clicked()
{
    user_name = ui->userName->text();
    user_passwd = ui->passwd->text();

    if(user_name.isEmpty() || user_passwd.isEmpty())
    {
        QMessageBox::warning(this,"错误","用户名、密码不能为空!");
    }
    else
    {
        //登陆链接
        QString Url = "https://www.example.com/user/login";
        QUrl serviceUrl(Url);
        QNetworkRequest request_registered(serviceUrl);

        // 设置SSL认证方式
        QSslConfiguration sslconfig;
        sslconfig.setPeerVerifyMode(QSslSocket::VerifyNone);     //这里并没有进行严格双向校验,具体设置参数请查看文档,仅作示例(这里是关键)
        //传输层安全协议,一般设为TlsV1_0 通用性高,具体要看服务器配置;
        sslconfig.setProtocol(QSsl::TlsV1_2);
        //sslconfig.setPeerVerifyDepth(1);

        //设置本地证书
        QFile keyFile("C:/Users/Administrator/Desktop/lock/release/cert/client.p12");
        bool openOK = keyFile.open(QIODevice::ReadWrite);    //读取本地证书

        QSslKey key;
        QSslCertificate certs;
        QList<QSslCertificate> caCerts;
        QByteArray passPhrase = QString("12345678").toLatin1();        //证书密码;
        openOK = QSslCertificate::importPkcs12(&keyFile, &key, &certs, &caCerts, passPhrase);
        keyFile.close();

        //配置SSL
        request_registered.setSslConfiguration(sslconfig);

        qDebug() << tr("正在进行注册...");
        //设置请求头
        request_registered.setRawHeader("Accept","*/*");
        request_registered.setRawHeader("Connection","keep-alive");
        request_registered.setHeader(QNetworkRequest::UserAgentHeader,"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36");
        request_registered.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");

        //首次登陆时候不需要带token
        //request_registered.setRawHeader(QByteArray("Authorization"), QByteArray("Token your_token"));

        //以表单形式POST 
        QUrlQuery postData;
        postData.addQueryItem("username", user_name);
        postData.addQueryItem("password", md5(user_passwd));
        qDebug() << tr("注册的用户名:") + user_name + tr("注册的密码:") + md5(user_passwd);

        //发起POST请求
        QNetworkReply* registered = m_accessManager_Registered->post(request_registered,postData.toString(QUrl::FullyEncoded).toUtf8());
        //清除
        user_name.clear();
        user_passwd.clear();
        postData.clear();
    }

}

响应请求,解析服务器返回的Json数据

void Widget::finishedSlot_Registered(QNetworkReply *registered)
{
    if (registered->error() == QNetworkReply::NoError)
    {
        // 获取响应信息
        QByteArray bytes = registered->readAll();      //读取所有字节;

        QJsonParseError jsonError;
        //转化为JSON文档
        QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
        // 解析Json  error
        if (doucment.isObject()) {
            QJsonObject obj = doucment.object();
            qDebug() << tr("打印obj");
            qDebug() << obj;

            QJsonValue val;
            QJsonValue data_value;
            if (obj.contains("message")) {
                QString succ_msg = obj.value("message").toString();
                ui->textBrowser->appendPlainText(tr("\n")+succ_msg);
                qDebug() << tr("打印massage");
                qDebug() << succ_msg;
            }
            if (obj.contains("error")) {
                val = obj.value("error");
                if ((val.toInt()) == 0) {
                    ui->textBrowser->appendPlainText("\nUser login succeeded!\n");
                    QMessageBox::information(this, "成功","登陆成功!");
                    ui->tripText->setText("用户登陆成功!");
                }
            }
        }
        if (jsonError.error != QJsonParseError::NoError) {
            ui->textBrowser->insertPlainText(tr("解析json失败"));
            qDebug() << QStringLiteral("解析Json失败");
            //return;
        }
    }
    else
    {
        QMessageBox::information(this, "警告","登陆失败!");

        qDebug()<<"handle errors here";
        QVariant statusCodeV = registered->attribute(QNetworkRequest::HttpStatusCodeAttribute);
        //statusCodeV是HTTP服务器的相应码,reply->error()是Qt定义的错误码,可以参考QT的文档

        ui->textBrowser->appendPlainText("\n found error ....code:" + statusCodeV.toInt());
        //ui->textBrowser->appendPlainText(statusCodeV.toInt());

        qDebug( "found error ....code: %d %d\n", statusCodeV.toInt(), (int)registered->error());

        qDebug(qPrintable(registered->errorString()));
    }

    registered->deleteLater();
}

这个例子展示了如何构建QT客户端与JAVA服务器的HTTPS通信,其中关键是本地证书的加载,及设置SSL的步骤,在QT5.1x之后,是支持直接加载.p12证书的,以前的版本只能通过OpenSS进行转换,因为QT默认是只支持OpenSSL的。在局域网内进行抓包发现,QT客户端发送的数据,是无法看到数据内容的,而服务器正确返回了登陆成功的JSON数据,所以这也验证了HTTPS加密通信已经通过。

最后修改:2020 年 05 月 15 日
您的支持就是我持续更新的动力!