扩展 MySQL 8.0  / 第 4 章 MySQL 插件 API  / 4.4 编写插件  / 4.4.9 编写认证插件  /  4.4.9.4 在身份验证插件中实现代理用户支持

4.4.9.4 在身份验证插件中实现代理用户支持

可插入身份验证使之成为可能的功能之一是代理用户(请参阅代理用户)。对于服务器端身份验证插件参与代理用户支持,必须满足以下条件:

  • 当连接客户端应被视为代理用户时,插件必须 authenticated_as在结构成员中 返回不同的名称MYSQL_SERVER_AUTH_INFO,以指示代理用户名。它还可以选择性地设置external_user成员,以设置 external_user系统变量的值。

  • 代理用户帐户必须设置为由插件进行身份验证。使用CREATE USERorGRANT 语句将帐户与插件相关联。

  • 代理用户帐户必须具有 PROXY代理帐户的权限。使用该 GRANT语句授予此权限。

换句话说,插件所需的代理用户支持的唯一方面是将其设置 authenticated_as为代理用户名。其余部分是可选的(设置 external_user)或由 DBA 使用 SQL 语句完成。

身份验证插件如何确定代理用户连接时返回哪个代理用户?这取决于插件。通常,插件根据服务器传递给它的身份验证字符串将客户端映射到代理用户。该字符串来自声明中指定使用插件进行身份验证 的子句 AS 部分。IDENTIFIED WITHCREATE USER

插件开发人员确定身份验证字符串的语法规则并根据这些规则实现插件。假设一个插件采用逗号分隔的成对列表,将外部用户映射到 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'

当服务器调用插件对客户端进行身份验证时,它会将适当的身份验证字符串传递给插件。该插件负责:

  1. 将字符串解析为其组件以确定要使用的映射

  2. 将客户端用户名与映射进行比较

  3. 返回正确的 MySQL 用户名

例如,如果extuser2example.com主机连接,服务器传递 'extuser1=mysqlusera, extuser2=mysqluserb'给插件,插件应该复制mysqluserbauthenticated_as, 带有终止空字节。如果extuser2example.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'