可插拔身份验证实现的功能之一是代理用户(请参阅 代理用户)。为了使服务器端身份验证插件参与代理用户支持,必须满足以下条件:
当应将连接客户端视为代理用户时,插件必须在
MYSQL_SERVER_AUTH_INFO
结构的authenticated_as
成员中返回不同的名称,以指示代理用户名。它还可以选择设置external_user
成员,以设置external_user
系统变量的值。必须设置代理用户帐户以通过插件进行身份验证。使用
CREATE USER
或GRANT
语句将帐户与插件关联。
换句话说,插件所需的代理用户支持的唯一方面是它将 authenticated_as
设置为代理用户名。其余部分是可选的(设置 external_user
)或由 DBA 使用 SQL 语句完成。
身份验证插件如何在代理用户连接时确定要返回哪个代理用户?这取决于插件。通常,插件会根据服务器传递给它的身份验证字符串将客户端映射到代理用户。此字符串来自 CREATE USER
语句的 IDENTIFIED WITH
子句的 AS
部分,该子句指定使用插件进行身份验证。
插件开发人员确定身份验证字符串的语法规则,并根据这些规则实现插件。假设一个插件采用一个逗号分隔的对列表,这些对将外部用户映射到 MySQL 用户。例如:
CREATE USER ''@'%.example.com'
IDENTIFIED WITH my_plugin AS 'extuser1=mysqlusera, extuser2=mysqluserb'
CREATE USER ''@'%.example.org'
IDENTIFIED WITH my_plugin AS 'extuser1=mysqluserc, extuser2=mysqluserd'
当服务器调用插件对客户端进行身份验证时,它会将适当的身份验证字符串传递给插件。插件负责:
将字符串解析为其组件以确定要使用的映射
将客户端用户名与映射进行比较
返回正确的 MySQL 用户名
例如,如果 extuser2
从 example.com
主机连接,则服务器会将 'extuser1=mysqlusera, extuser2=mysqluserb'
传递给插件,并且插件应将 mysqluserb
复制到 authenticated_as
中,并以终止空字节结尾。如果 extuser2
从 example.org
主机连接,则服务器会传递 'extuser1=mysqluserc, extuser2=mysqluserd'
,并且插件应改为复制 mysqluserd
。
如果映射中没有匹配项,则操作取决于插件。如果需要匹配项,则插件可能会返回错误。或者插件可能只返回客户端名称;在这种情况下,它不应该更改 authenticated_as
,并且服务器不会将客户端视为代理。
以下示例演示了如何使用名为 auth_simple_proxy
的插件处理代理用户。与前面描述的 auth_simple
插件一样,auth_simple_proxy
接受任何非空密码为有效密码(因此不应在生产环境中使用)。此外,它还会检查 auth_string
身份验证字符串成员,并使用这些非常简单的规则来解释它:
如果字符串为空,则插件按原样返回用户名,并且不进行代理。也就是说,插件不会更改
authenticated_as
的值。如果字符串非空,则插件将其视为代理用户的名称,并将其复制到
authenticated_as
中,以便进行代理。
为了进行测试,请根据前面的规则设置一个未代理的帐户和一个代理的帐户。这意味着一个帐户没有 AS
子句,而另一个帐户包含一个命名代理用户的 AS
子句:
CREATE USER 'plugin_user1'@'localhost'
IDENTIFIED WITH auth_simple_proxy;
CREATE USER 'plugin_user2'@'localhost'
IDENTIFIED WITH auth_simple_proxy AS 'proxied_user';
此外,为代理用户创建一个帐户,并为其授予 plugin_user2
的 PROXY
权限:
CREATE USER 'proxied_user'@'localhost'
IDENTIFIED BY 'proxied_user_pass';
GRANT PROXY
ON 'proxied_user'@'localhost'
TO 'plugin_user2'@'localhost';
在服务器调用身份验证插件之前,它会将 authenticated_as
设置为客户端用户名。为了指示用户是代理,插件应将 authenticated_as
设置为代理用户名。对于 auth_simple_proxy
,这意味着它必须检查 auth_string
值,并且,如果该值非空,则将其复制到 authenticated_as
成员中,以将其作为代理用户的名称返回。此外,当发生代理时,插件会将 external_user
成员设置为客户端用户名;这将成为 external_user
系统变量的值。
static int auth_simple_proxy_server (MYSQL_PLUGIN_VIO *vio,
MYSQL_SERVER_AUTH_INFO *info)
{
unsigned char *pkt;
int pkt_len;
/* read the password as null-terminated string, fail on error */
if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
return CR_ERROR;
/* fail on empty password */
if (!pkt_len || *pkt == '\0')
{
info->password_used= PASSWORD_USED_NO;
return CR_ERROR;
}
/* accept any nonempty password */
info->password_used= PASSWORD_USED_YES;
/* if authentication string is nonempty, use as proxied user name */
/* and use client name as external_user value */
if (info->auth_string_length > 0)
{
strcpy (info->authenticated_as, info->auth_string);
strcpy (info->external_user, info->user_name);
}
return CR_OK;
}
成功连接后,USER()
函数应指示连接的客户端用户名和主机名,而 CURRENT_USER()
应指示在会话期间应用其权限的帐户。如果未发生代理,则后一个值应该是连接用户帐户;如果发生了代理,则应该是代理帐户。
编译并安装插件,然后对其进行测试。首先,以 plugin_user1
身份连接:
$> mysql --user=plugin_user1 --password
Enter password: x
在这种情况下,不应该进行代理:
mysql> SELECT USER(), CURRENT_USER(), @@proxy_user, @@external_user\G
*************************** 1. row ***************************
USER(): plugin_user1@localhost
CURRENT_USER(): plugin_user1@localhost
@@proxy_user: NULL
@@external_user: NULL
然后以 plugin_user2
身份连接:
$> mysql --user=plugin_user2 --password
Enter password: x
在这种情况下,plugin_user2
应该被代理到 proxied_user
:
mysql> SELECT USER(), CURRENT_USER(), @@proxy_user, @@external_user\G
*************************** 1. row ***************************
USER(): plugin_user2@localhost
CURRENT_USER(): proxied_user@localhost
@@proxy_user: 'plugin_user2'@'localhost'
@@external_user: 'plugin_user2'@'localhost'