如何在mysql中间件层实现客户端连…

来源:互联网 发布:上海家博会数据 编辑:程序博客网 时间:2024/06/02 01:58

很多情况下,mysql中间件程序会集成连接池功能,并且客户端的登录用户和实际操作后端数据库的用户为不同的用户。也就是说,中间件程序在客户端登录之前已经使用某个特定的用户名和密码建立了中间件到后端mysql的连接池;客户登入验证通过后就可以直接使用连接池中的连接。

 

这样设计的好处是可以多个用户共享一个连接池,即避免了每个客户重新创建连接的开销,也避免了为每个客户

准备单独的连接池所带来的浪费。(当然多个用户共享一个连接池还需要有另外的控制机制保证它们的互不干扰。)

 

 而这样设计也带来了客户端连接操作的权限认证问题。因为客户端登录之后使用的是另一个用户名去操作后端数据库,通常这个用户名拥有比客户端登录用户更大的权限,这就导致了权限被放大的问题。

 

简单的解决办法是为每个登录客户保留它们登录时的认证连接,每次操作之前使用该认证连接查看权限。

 

这个方法最直接也最简单,但要为每个登录客户端保留一个连接代价有点高,这限制了中间件程序的客户端并发数。并且作为中间件程序,我们可能不需要那么完整的权限控制;很多情况下业务是以库进行区分的,某些场景下只需要确定这个客户端连接是否有权限操作某个库就足够了。

 

另一个方法是在需要的时候模拟一次客户端登录重新创建一个认证连接。这样做的好处是按需使用连接,避免为每个客户端保留一个认证连接的高昂代价;如果中间件程序登录后的权限认证频率不高,这是非常推荐一种方法。当然它的难点是你没有客户端登录的密码,如何去模拟登录呢?

 

首先看下mysql客户端是如何登录的,如下是代码示例。代码的输入为客户端密码,服务端返回的随机加密串。

 

 SHA1_CONTEXT sha1_context;
uint8_t hash_stage1[SHA1_HASH_SIZE];
uint8_t hash_stage2[SHA1_HASH_SIZE];

mysql_sha1_reset(&sha1_context);

mysql_sha1_input(&sha1_context, (uint8_t *) password, (unsignedint) strlen(password));
mysql_sha1_result(&sha1_context, hash_stage1);

mysql_sha1_reset(&sha1_context);
mysql_sha1_input(&sha1_context, hash_stage1,SHA1_HASH_SIZE);
mysql_sha1_result(&sha1_context, hash_stage2);
;
mysql_sha1_reset(&sha1_context);
mysql_sha1_input(&sha1_context, (const uint8_t *) message,SCRAMBLE_LENGTH);
mysql_sha1_input(&sha1_context, hash_stage2,SHA1_HASH_SIZE);

mysql_sha1_result(&sha1_context, (uint8_t *) to);
my_crypt(to, (const unsigned char *) to, hash_stage1,SCRAMBLE_LENGTH);

 

可以看到首先通过对客户端密码的hash获得hash_stage1,然后通过对hash_stage1的再hash获得hash_stage2。然后通过将hash_stage2和服务端随机串的hash结果与hash_stage1进行加密(异或操作)生产最终发给服务端的认证串。

 

所以,我们只需要能获取到hash_stage1和hash_stage2就可以不用客户端密码直接登录了。

 

hash_stage2其实就保存在服务端的mysql.user表的password字段中,当然需要进行一下简单的转换,将它转成2进制(参考mysql代码,使用函数hex2octet)。而hash_stage1,参考mysql服务端的认证代码则可以通过hash_stage2,服务端随机认证串以及对应的客户端最终发往服务端的认证串计算出来。

 

获取到hash_stage2和hash_stage1之后就可以根据新的服务端认证串进行登录了。

 

如下是示例代码,输入为

1. mysql.user表的password字段,代码中为 dbscale_password

2. 客户端登录时获取的服务端随机认证串,代码中为 message
3. 客户端登录时最终发往服务端的认证串,代码中为 to

4. 这次模拟登录获取的服务端新随机认证串,代码中为  new_message

 

 

 SHA1_CONTEXT sha1_context;

uint8_t hash_dbscale_s2[SHA1_HASH_SIZE];

// get hash_stage2 using the password in mysql.user
hex2octet(hash_dbscale_s2, dbscale_password, SHA1_HASH_SIZE);

uint8_t hash_dbscale_s1[SHA1_HASH_SIZE];

mysql_sha1_reset(&sha1_context);


mysql_sha1_input(&sha1_context, (const uint8_t *) message,SCRAMBLE_LENGTH);
mysql_sha1_input(&sha1_context, hash_dbscale_s2,SHA1_HASH_SIZE);
mysql_sha1_result(&sha1_context, hash_dbscale_s1);


my_crypt((char *) hash_dbscale_s1, (const unsigned char*)hash_dbscale_s1, (const unsigned char *)to, SCRAMBLE_LENGTH);

 

 

上面的代码首先通过mysql.user表的password字段算出hash_stage2,然后通过客户端登录时获取的服务端随机认证串message 和 客户端登录时最终发往服务端的认证串 to, 计算出hash_stage1.

 


;
mysql_sha1_reset(&sha1_context);
mysql_sha1_input(&sha1_context, (const uint8_t *) new_message,SCRAMBLE_LENGTH);
mysql_sha1_input(&sha1_context, hash_dbscale_s2,SHA1_HASH_SIZE);

mysql_sha1_result(&sha1_context, (uint8_t *) to);
my_crypt(to, (const unsigned char *) to, (const unsigned char*)hash_dbscale_s1, SCRAMBLE_LENGTH);

 

上面的代码为通过hash_stage1和hash_stage2,安装新的服务端随机认证串,生产新的最终认证串,进行模拟登录。

 

 

 

转载请注明转自高孝鑫的博客

0 0