drogon icon indicating copy to clipboard operation
drogon copied to clipboard

连接远程数据库和本地数据库报错

Open mrclassfree opened this issue 6 months ago • 1 comments

版本是使用vcpkg安装的 1.9.10版本, 数据库是MYSQL8.0,使用Navacat连接数据库一切正常。 使用Drogon框架连接数据库报错,

  • 当数据库是本地数据库127.0.0.1时,报错: “Lost connection to server_during query" - MysqlConnection.cc:333

  • 当数据库是远程数据库时,报错: TLS/SSL error: Server certificate validation failed. A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider. Error 0x800B0109(CERT_E_UNTRUSTEDROOT)" - MysqlConnection.cc:333

同样的数据库和相同的配置信息,Linux版Drogon连接一切正常。 错误截图,我粘在附件中。

Image Image

mrclassfree avatar May 23 '25 06:05 mrclassfree

解决了吗,同该问题

qiee996 avatar Jun 05 '25 07:06 qiee996

解决了吗,同该问题

没有解决。通过在mysql配置文件中增加ssl=0,问题依旧。 补充以下,mysql组件是使用vcpkg安装的 libmariadb:x64-windows 3.4.1

mrclassfree avatar Jun 24 '25 01:06 mrclassfree

我也是,我设置了

 // Disable SSL enforcement for MariaDB using mysql_optionsv
        bool ssl_enforce = false;
        mysql_optionsv(mysqlPtr_.get(), MYSQL_OPT_SSL_ENFORCE, &ssl_enforce);

还是不行呀!

wuxianggujun avatar Aug 18 '25 02:08 wuxianggujun

需要新的PR支持SSL,#2367

an-tao avatar Aug 18 '25 06:08 an-tao

需要新的PR支持SSL,#2367

你的这个修改的pr,我尝试过还是会报错2026错误码问题。于是我自己修改,现在已经不会报错ssl问题了

wuxianggujun avatar Aug 18 '25 06:08 wuxianggujun

需要新的PR支持SSL,#2367

你的这个修改的pr,我尝试过还是会报错2026错误码问题。于是我自己修改,现在已经不会报错ssl问题了

能做个PR吗,谢谢

an-tao avatar Aug 18 '25 07:08 an-tao

需要新的PR支持SSL,#2367

你的这个修改的pr,我尝试过还是会报错2026错误码问题。于是我自己修改,现在已经不会报错ssl问题了

能做个PR吗,谢谢

我仔细看了一下,其实你的应该再添加这么一段应该就可以了。mysql_options(mysql, MYSQL_OPT_SSL_ENFORCE, 0);,下面是我修改后的构造方法代码

MysqlConnection::MysqlConnection(trantor::EventLoop *loop,
                                 const std::string &connInfo)
    : DbConnection(loop),
      mysqlPtr_(std::shared_ptr<MYSQL>(new MYSQL, [](MYSQL *p) {
          mysql_close(p);
          delete p;
      }))
{
    static MysqlEnv env;
    static thread_local MysqlThreadEnv threadEnv;
    mysql_init(mysqlPtr_.get());
    mysql_options(mysqlPtr_.get(), MYSQL_OPT_NONBLOCK, nullptr);
    
    // Set default authentication plugin to mysql_native_password before parsing config
    if (mysql_options(mysqlPtr_.get(), MYSQL_DEFAULT_AUTH, "mysql_native_password") == 0)
    {
        LOG_DEBUG << "Pre-configured default auth plugin to mysql_native_password";
    }
    else
    {
        LOG_WARN << "Failed to pre-configure default auth plugin";
    }
    
#ifdef HAS_MYSQL_OPTIONSV
    mysql_optionsv(mysqlPtr_.get(), MYSQL_OPT_RECONNECT, &reconnect_);
#endif
    // Get the key and value
    auto connParams = parseConnString(connInfo);
    for (auto const &kv : connParams)
    {
        auto key = kv.first;
        auto value = kv.second;

        std::transform(key.begin(),
                       key.end(),
                       key.begin(),
                       [](unsigned char c) { return tolower(c); });
        LOG_DEBUG << "connInfo key:[" << key << "] value:[" << value << "]";

        if (key == "host")
        {
            host_ = value;
        }
        else if (key == "user")
        {
            user_ = value;
        }
        else if (key == "dbname")
        {
            dbname_ = value;
        }
        else if (key == "port")
        {
            port_ = value;
        }
        else if (key == "password")
        {
            passwd_ = value;
        }
        else if (key == "client_encoding")
        {
            characterSet_ = value;
        }
        else if (key == "ssl_mode")
        {
            sslMode_ = value;
        }
        else if (key == "ssl_cert")
        {
            sslCert_ = value;
        }
        else if (key == "ssl_key")
        {
            sslKey_ = value;
        }
        else if (key == "ssl_ca")
        {
            sslCa_ = value;
        }
        else if (key == "ssl_capath")
        {
            sslCaPath_ = value;
        }
        else if (key == "ssl_cipher")
        {
            sslCipher_ = value;
        }
        else if (key == "default_auth")
        {
            // Force default authentication plugin
            if (mysql_options(mysqlPtr_.get(),
                              MYSQL_DEFAULT_AUTH,
                              value.c_str()) == 0)
            {
                LOG_DEBUG << "Default auth plugin set to: " << value;
            }
            else
            {
                LOG_WARN << "Failed to set default auth plugin to: " << value;
            }
        }
    }

    // Configure SSL options based on sslMode_
    LOG_DEBUG << "Configuring SSL options based on sslMode_: " << sslMode_;

    // Convert sslMode_ to uppercase for comparison
    std::string ssl_mode_upper = sslMode_;
    std::transform(ssl_mode_upper.begin(), ssl_mode_upper.end(), ssl_mode_upper.begin(),
                   [](unsigned char c) { return std::toupper(c); });

    if (ssl_mode_upper == "DISABLED")
    {
        LOG_DEBUG << "SSL mode is DISABLED - attempting to disable SSL";
        
        // Method 3: Explicitly set protocol to TCP to avoid SSL
        enum mysql_protocol_type protocol = MYSQL_PROTOCOL_TCP;
        if (mysql_options(mysqlPtr_.get(), MYSQL_OPT_PROTOCOL, &protocol) == 0)
        {
            LOG_DEBUG << "Protocol explicitly set to TCP";
        }
        else
        {
            LOG_DEBUG << "Failed to set protocol to TCP";
        }

        // Method 4: Try multiple SSL-related options to ensure SSL is completely off
        my_bool ssl_disabled = 0;
        mysql_options(mysqlPtr_.get(), MYSQL_OPT_SSL_ENFORCE, &ssl_disabled);
        mysql_options(mysqlPtr_.get(), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &ssl_disabled);
        
        // Set all SSL context to empty strings to prevent any SSL usage
        mysql_ssl_set(mysqlPtr_.get(), "", "", "", "", "");
    }
    else if (ssl_mode_upper == "REQUIRED" || !sslCert_.empty() || !sslKey_.empty() || 
             !sslCa_.empty() || !sslCaPath_.empty() || !sslCipher_.empty())
    {
        LOG_DEBUG << "SSL mode is REQUIRED or SSL certificates provided - configuring SSL";
        
        // SSL is required/enabled or certificates are provided
        mysql_ssl_set(mysqlPtr_.get(),
                      sslKey_.empty() ? nullptr : sslKey_.c_str(),
                      sslCert_.empty() ? nullptr : sslCert_.c_str(),
                      sslCa_.empty() ? nullptr : sslCa_.c_str(),
                      sslCaPath_.empty() ? nullptr : sslCaPath_.c_str(),
                      sslCipher_.empty() ? nullptr : sslCipher_.c_str());
        LOG_DEBUG << "SSL certificates configured";
    }
    else
    {
        LOG_DEBUG << "SSL mode is PREFERRED or unspecified - using default behavior";
    }
    
}

然后测试我服务器关闭SSL的MYSQL服务就会提示

20250820 03:14:10.278000 UTC 1152 ERROR Error(1045) "Plugin caching_sha2_password could not be loaded: �Ҳ���ָ����ģ�顣 Library path is 'caching_sha2_password.dll'" - MysqlConnection.cc:433
20250820 03:14:10.278000 UTC 1152 ERROR Failed to mysql_real_connect() - MysqlConnection.cc:435

这个其实也好解决,复制dll到exe可执行文件的目录下就可以了。我之前都是这样子解决了这个ssl验证问题。然后下面这段是我让ai分析的,希望可以给你一个参考意见。短答案:**你这版之所以“能禁用 SSL”,核心不是某个单一开关,而是你在 ssl_mode == DISABLED 分支里用一组设置把 TLS 协商“故意搞失败”,而且又没强制必须走 TLS,于是客户端自然回退到明文。**具体起作用的点有这几个——

  1. 空密码套件 → TLS 无法建立 你调用了
mysql_ssl_set(mysql, "", "", "", "", "");

这个函数的最后一个参数是允许的 cipher 列表。在 MySQL 的语义里,把可用 cipher 列表设成空字符串就等价于“没有任何可用 cipher”——加密连接无法建立。一旦握手失败、且你没有“强制 TLS”,客户端就会退回到非加密连接。官方文档明确:客户端/服务器侧若把 TLS(ciphersuites) 设为空字符串,就无法建立加密连接。([dev.mysql.com][1])

  1. 没有强制 TLS(enforce=0)→ 可以回退 你随后又把
mysql_options(mysql, MYSQL_OPT_SSL_ENFORCE, &0);

设成了 不强制。这意味着“握手失败可以降级到明文”,恰好配合上面“空 cipher 导致握手失败”,于是走了明文路径。(在新版本 MySQL 里这个选项已被 MYSQL_OPT_SSL_MODE 取代并标记废弃,但在你当前库里仍然有效。)([Oracle 文档][2])

  1. 默认 SSL 模式没被你改写 → 仍是 PREFERRED 你没有调用 MYSQL_OPT_SSL_MODE,所以库保持默认 SSL_MODE_PREFERRED:能加密就加,不能就明文。由于你把 cipher 清空让“能加密”这条路走不通,就触发了“不能 → 明文”的分支。([dev.mysql.com][3])

  2. 把默认认证插件改成 mysql_native_password → 保障了明文下也能认证 你在连接前 MYSQL_DEFAULT_AUTH = "mysql_native_password",避免了 MySQL 8 默认的 caching_sha2_password非 TLS下经常需要额外 RSA 公钥获取或干脆失败的情况(你之前 1045 错误就是这类问题)。这一步让明文连接能顺利认证。([dev.mysql.com][4])

反而,MYSQL_OPT_PROTOCOL = TCP 与“禁不禁用 TLS”无关——TLS 本来就是跑在 TCP 上的;它只是确保走 TCP 而不是本地 socket/命名管道。


对应到你这段代码,真正“生效”的行

  • mysql_options(..., MYSQL_DEFAULT_AUTH, "mysql_native_password"):避免非 TLS 下 caching_sha2_password 认证问题。([dev.mysql.com][4])
  • mysql_options(..., MYSQL_OPT_SSL_ENFORCE, &0):不强制 TLS,可回退到明文。([Oracle 文档][2])
  • mysql_ssl_set(mysql, "", "", "", "", ""):把 cipher 列表置空,让 TLS 握手必失败。([dev.mysql.com][1])

三者叠加 ⇒ 握手失败但又允许降级 ⇒ 实际走明文。


给你两个小提醒(不改也能跑,但更稳妥)

  • 这套“空 cipher 让握手失败”的做法有点“技巧性”。更显式/前向兼容的写法是直接设置:

    unsigned int mode = SSL_MODE_DISABLED;
    mysql_options(mysql, MYSQL_OPT_SSL_MODE, &mode);
    

    这样就算将来库把 MYSQL_OPT_SSL_ENFORCE 移除了,你也不会受影响。([dev.mysql.com][3])

  • 连接成功后加一句自检日志更安心:

    const char* ciph = mysql_get_ssl_cipher(mysql);
    // ciph == NULL 说明是明文;非 NULL 说明用了 TLS
    

    官方说明:返回 NULL 表示没有使用 SSL。([ftp.twaren.net][5])


可能的边界情况

  • 若你的 OpenSSL/服务器启用 TLS 1.3ssl_cipher(TLS ≤1.2)和 tls_ciphersuites(TLS 1.3)是两套列表;极端情况下,仅清空旧的 cipher 列表而不清空 TLS 1.3 ciphersuites,TLS 1.3 仍可能握手成功。要万无一失,配合 C API 的 MYSQL_OPT_TLS_CIPHERSUITES 也置空。([dev.mysql.com][1])
  • 如果服务器强制要求 TLS,你的做法会导致连接直接失败(这是符合预期的:你就是想禁用 TLS)。

wuxianggujun avatar Aug 20 '25 03:08 wuxianggujun

解决了吗,同该问题

没有解决。通过在mysql配置文件中增加ssl=0,问题依旧。 补充以下,mysql组件是使用vcpkg安装的 libmariadb:x64-windows 3.4.1

按照我的方法如果不需要在json配置的话,只需要修改一下orm_lib/src/mysql_impl/MysqlConnection.cc源码即可。

wuxianggujun avatar Aug 20 '25 03:08 wuxianggujun