笔者作为Apache Doris的开发者,平时感觉相关Doris的文章写的很少。主要是很多时候不知道应该去记录一些怎么样的问题,感觉写的不好就会很慌张。新的一年,希望记录自己在Doris开发过程之中所遇到一些有意思的事情。(只希望能坚持下来,别打脸~~)
言归正传,回到本篇想聊的问一个问题,笔者在开发ODBC of Doris的工作之中,发现通过MySQL 8.0的Driver连接Doris总是提示密码验证失败。但是由于开发工作繁忙,一直没有腾出手解决这个问题。最近重新抽时间梳理了一下这个问题,这个问题本身不难解决,但是解决问题的思路我觉得值得与大家分享,献丑了啊,各位~~

1.老革命遇到新问题

使用MySQL 8.0的客户端连接Doris时,如果不添加如下参数--default-auth=mysql_native_password的话,总会出现如下提示的密码认证错误:

ERROR 1045 (28000): Access denied for user 'default_cluster:test' (using password: YES)

同样的密码认证问题也会同时出现在了使用ODBC的MySQL 8.0以上的Driver连接Doris时。更令人蛋疼的是,使用ODBC链接时并没法调用上面的参数进行问题的规避。这会带来两个问题:

  1. Doris本身的ODBC外表无法通过MySQL 8.0以上的Driver连接Doris
2.许多流行的BI分析工具如Tableau等:也无法通过ODBC的方式连接Doris

之前通过5.x的客户端和Driver可以顺利的连接Doris,而现在真是老革命遇上新问题了。

默认的密码认证插件的变更

其实新问题的引入很简单,就是MySQL的客户端从8.0的版本开始,将原先客户端的默认的密码认证插件由mysql_native_password改为了caching_sha2_password,两种密码认证方式不同。而Doris当前只支持mysql_native_password的密码认证插件,所以就导致了连接时密码认证失败了。而关于密码认证插件的变更,更为详细的内容,可以参考MySQL的官方文档。

2.问题的分析与梳理

好的,确认了问题,就开始研究解决方案。从直觉上说,Doris支持新的caching_sha2_password密码认证插件肯定是最直接的解决思路。这种做法肯定是一劳永逸的解决问题的,但是这就得重构整个Doris的密码管理系统,开发和支持起来的代价实在是有些太大了。

那既然我们否定了这种方式,就得另外想办法解决了。首先,使用MySQL 8.0的客户端连接Doris时,添加如下参数--default-auth=mysql_native_password便可以认证成功。 所以问题就回到了如何让ODBC的连接能够支持上述参数,笔者经历了下面的分析历程:

2.1 ODBC连接文档

ODBC是通过连接串的方式传参给MySQL的连接Driver的,如果能够像使用MySQL客户端的方式添加参数便可以解决,那么自然无代码的Coding是成本最低的解决方案。

笔者首先尝试查看了MySQL官方的ODBC连接参数文档,遗憾的是,并没有找到ODBC关于认证方式的任何内容,这也就意为着:此路不通。

2.2 新旧版本的兼容性

既然MySQL从8.0开始切换了默认的密码认证插件,那么新的客户端是否可以连接老的MySQL服务器呢?MySQL本身是如何解决新老客户端的兼容问题的呢?

于是笔者尝试使用MySQL 8.0的客户端连接了MySQL的5.x的服务器,发现了下面的线索:新客户端并不需要像连接Doris一样,修改默认的密码认证插件。那也就意味着,MySQL的客户端和服务器可以在连接过程之中通过某种方式交换确认一种服务器支持的密码认证方式。

既然如此,笔者开始了Google之旅,但是并没有搜索到什么有价值的信息。没办法,源码面前,了无秘密。于是笔者决定尝试阅读一下MySQL Client端的代码,看看是否能发现上述的交互逻辑。

经过一番"痛苦"的源码搜索和阅读,笔者在找到了如下的注释,完整的阐述了MySQL的客户端与服务器的连接过程:

  1. The client connects to the server
  2. The server sends @ref page_protocol_connection_phase_packets_protocol_handshake
  3. The client respons with
  @ref page_protocol_connection_phase_packets_protocol_handshake_response
  4. The server sends the
  @ref page_protocol_connection_phase_packets_protocol_auth_switch_request to tell
  the client that it needs to switch to a new authentication method.
  5. Client and server possibly exchange further packets as required by the server
  authentication method for the user account the client is trying to authenticate
  against.
  6. The server responds with an @ref page_protocol_basic_ok_packet or rejects
    with @ref page_protocol_basic_err_packet

把上述的注释读懂之后,笔者又回头查阅了一下Doris之中处理MySQL客户端连接的代码。总算是整明白了为啥新的客户端连接Doris会失败了,这个是新客户端连接Doris的流程:

Drois  ->: Authentication Plugin: mysql_native_password
Client <-: Client Auth Plugin: caching_sha2_password
Doris  ->: MySQL Error 2012 (HY000): Password check failed.

而新客户端连接老的MySQL的流程如下:

Mysql  ->: Authentication Plugin: mysql_native_password
Client <-: Client Auth Plugin: caching_sha2_password
Mysql  ->: Auth Switch Request: Auth Method Name: mysql_native_password
Client <-: Auth Switch Response
Mysql  ->: OK

MySQL的服务器支持了Auth Switch Request的网络请求来告知客户端自己支持的认证的密码插件,而客户端会进行密码插件的支持检查,而客户端则将密码插件加密的结果返回。

3.开发起来,解决问题

通过上一小节的分析,问题已经水落石出了。接下来就是如何在Doris上支持Auth Switch Request的网络请求。

3.1 确认二进制结构

所以这里就需要研究这两个Auth Switch Request和Auth Switch Response的二进制包是如何组成的。这里再次借助官方文档,确认了两个包的组成:

Doris开发手记1:解决蛋疼的MySQL 8.0连接问题_Doris开发

Doris开发手记1:解决蛋疼的MySQL 8.0连接问题_Doris开发_02

3.2 代码开发

其实到这里的工作已经很简单了,直接上笔者修改Doris的代码吧:

if (!handshakePacket.checkAuthPluginSameAsDoris(authPacket.getPluginName())) {
            // 1. clear the serializer
            serializer.reset();
            // 2. build the auth switch request and send to the client
            handshakePacket.buildAuthSwitchRequest(serializer);
            channel.sendAndFlush(serializer.toByteBuffer());
            // Server receive auth switch response packet from client.
            ByteBuffer authSwitchResponse = channel.fetchOnePacket();
            if (authSwitchResponse == null) {
                // receive response failed.
                return false;
            }
            // 3. the client use default password plugin of Doris to dispose
            // password
            authPacket.setAuthResponse(readEofString(authSwitchResponse));
        }

就是进行了密码认证插件的校验,如果不Match Doris默认的密码认证插件的话,则构造AuthSwitchRequest发送给客户端。(笔者这里只列出了部分代码,完整的代码修改请参考如下的pr .

Coding完成之后,编译部署,进行测试,问题解决,提出issue,把解决问题的代码贡献给Doris的官方代码仓库提pr。完结撒花~~~~

4.小结

Bingo! 到此为止,问题顺利解决了,希望通过和大家分享一个问题的解决流程,帮助大家梳理数据库开发之中的解决问题的思路。我们有着最大的三个帮手

  • 搜索引擎
  • 官方文档
  • 源代码

而如果你遇到的是Doris的问题,那你就有第四个帮手了:百度Doris团队。(加星重点)

当然,这里也留下一个TODO的问题:支持MySQL 8.0默认的caching_sha2_password的认证方式。相较原先的mysql_native_password的认证方式,它有一定的安全性优势,但是这样可能需要重构整个Doris的密码体系了。

最后,也希望大家多多支持Apache Doris,多多给Doris贡献代码,感恩~~

5.参考资料

MySQL官方文档
MySQL源代码
Apache Doris源代码