扩展 MySQL 8.4  /  ...  /  编写服务器端身份验证插件

4.4.9.1 编写服务器端身份验证插件

使用所有服务器插件类型通用的常规描述符格式声明服务器端插件(参见 第 4.4.2.1 节,“服务器插件库和插件描述符”)。对于 auth_simple 插件,描述符如下所示

mysql_declare_plugin(auth_simple)
{
  MYSQL_AUTHENTICATION_PLUGIN,
  &auth_simple_handler,                 /* type-specific descriptor */
  "auth_simple",                        /* plugin name */
  "Author Name",                        /* author */
  "Any-password authentication plugin", /* description */
  PLUGIN_LICENSE_GPL,                   /* license type */
  NULL,                                 /* no init function */
  NULL,                                 /* no deinit function */
  0x0100,                               /* version = 1.0 */
  NULL,                                 /* no status variables */
  NULL,                                 /* no system variables */
  NULL,                                 /* no reserved information */
  0                                     /* no flags */
}
mysql_declare_plugin_end;

name 成员 (auth_simple) 指示在诸如 INSTALL PLUGINUNINSTALL PLUGIN 等语句中引用插件时要使用的名称。这也是 SHOW PLUGINSINFORMATION_SCHEMA.PLUGINS 显示的名称。

常规描述符的 auth_simple_handler 成员指向类型特定的描述符。对于身份验证插件,类型特定的描述符是 st_mysql_auth 结构的实例(在 plugin_auth.h 中定义)

struct st_mysql_auth
{
  int interface_version;
  const char *client_auth_plugin;
  int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info);
  int (*generate_authentication_string)(char *outbuf,
      unsigned int *outbuflen, const char *inbuf, unsigned int inbuflen);
  int (*validate_authentication_string)(char* const inbuf, unsigned int buflen);
  int (*set_salt)(const char *password, unsigned int password_len,
                  unsigned char* salt, unsigned char *salt_len);
  const unsigned long authentication_flags;
};

st_mysql_auth 结构具有以下成员

  • interface_version:类型特定的 API 版本号,始终为 MYSQL_AUTHENTICATION_INTERFACE_VERSION

  • client_auth_plugin:客户端插件名称

  • authenticate_user:指向与客户端通信的主插件函数的指针

  • generate_authentication_string:指向生成身份验证字符串的密码摘要的插件函数的指针

  • validate_authentication_string:指向验证密码摘要的插件函数的指针

  • set_salt:指向将加扰密码转换为二进制形式的插件函数的指针

  • authentication_flags:标志字

client_auth_plugin 成员应指示客户端插件的名称,如果需要特定插件。值为 NULL 表示“任何插件。 在后一种情况下,客户端使用的任何插件都可以。如果服务器插件不关心客户端插件或它发送的用户名或密码,这将很有用。例如,如果服务器插件仅验证本地客户端并使用操作系统的一些属性而不是客户端插件发送的信息,则可能是这种情况。

对于 auth_simple,类型特定的描述符如下所示

static struct st_mysql_auth auth_simple_handler =
{
  MYSQL_AUTHENTICATION_INTERFACE_VERSION,
  "auth_simple",             /* required client-side plugin name */
  auth_simple_server         /* server-side plugin main function */
  generate_auth_string_hash, /* generate digest from password string */
  validate_auth_string_hash, /* validate password digest */
  set_salt,                  /* generate password salt value */
  AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE
};

主函数 auth_simple_server() 接受两个参数,分别代表 I/O 结构和 MYSQL_SERVER_AUTH_INFO 结构。该结构定义在 plugin_auth.h 中,如下所示

typedef struct st_mysql_server_auth_info
{
  char *user_name;
  unsigned int user_name_length;
  const char *auth_string;
  unsigned long auth_string_length;
  char authenticated_as[MYSQL_USERNAME_LENGTH+1];
  char external_user[512];
  int  password_used;
  const char *host_or_ip;
  unsigned int host_or_ip_length;
} MYSQL_SERVER_AUTH_INFO;

字符串成员的字符集为 UTF-8。如果与字符串相关联的 _length 成员,则它表示字符串的字节长度。字符串也以 null 结尾。

当服务器调用身份验证插件时,它应将 MYSQL_SERVER_AUTH_INFO 结构成员解释如下。如所示,其中一些用于设置客户端会话内的 SQL 函数或系统变量的值。

  • user_name:客户端发送的用户名。该值成为 USER() 函数值。

  • user_name_lengthuser_name 的字节长度。

  • auth_stringmysql.user 系统表中匹配帐户名的行的 authentication_string 列的值(即与客户端用户名和主机名匹配的行,服务器使用它来确定如何验证客户端)。

    假设您使用以下语句创建一个帐户

    CREATE USER 'my_user'@'localhost'
      IDENTIFIED WITH my_plugin AS 'my_auth_string';

    my_user 从本地主机连接时,服务器将调用 my_plugin 并将 'my_auth_string' 传递给它作为 auth_string 值。

  • auth_string_lengthauth_string 的字节长度。

  • authenticated_as:服务器将其设置为用户名(user_name 的值)。插件可以更改它以指示客户端应该具有不同用户的权限。例如,如果插件支持代理用户,则初始值为连接(代理)用户的名称,插件可以将此成员更改为代理的用户名。然后,服务器将代理用户视为具有代理用户的权限(假设满足代理用户支持的其他条件;参见 第 4.4.9.4 节,“在身份验证插件中实现代理用户支持”)。该值表示为一个字符串,最多为 MYSQL_USER_NAME_LENGTH 字节长,加上一个终止的 null。该值成为 CURRENT_USER() 函数值。

  • external_user:服务器将其设置为空字符串(以 null 结尾)。它的值成为 external_user 系统变量值。如果插件希望该系统变量具有不同的值,则应相应地设置此成员(例如,设置为连接用户名)。该值表示为一个字符串,最多为 511 字节长,加上一个终止的 null。

  • password_used:此成员仅在身份验证失败时适用。插件可以设置它或忽略它。该值用于构造 Authentication fails. Password used: %s 的失败错误消息。 password_used 的值决定如何处理 %s,如下表所示。

    password_used %s 处理
    0
    1
    2 将没有 %s
  • host_or_ip:客户端主机的名称(如果可以解析),否则为 IP 地址。

  • host_or_ip_lengthhost_or_ip 的字节长度。

auth_simple 主函数 auth_simple_server() 从客户端读取密码(以 null 结尾的字符串),如果密码非空(第一个字节不为 null),则成功

static int auth_simple_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;

  return CR_OK;
}

主函数应返回以下表中所示的错误代码之一。

错误代码 意义
CR_OK 成功
CR_OK_HANDSHAKE_COMPLETE 不要将状态包发回客户端
CR_ERROR 错误
CR_AUTH_USER_CREDENTIALS 身份验证失败
CR_AUTH_HANDSHAKE 身份验证握手失败
CR_AUTH_PLUGIN_ERROR 内部插件错误

有关握手工作原理的示例,请参见 plugin/auth/dialog.c 源文件。

服务器在性能模式 host_cache 表中计算插件错误。

auth_simple_server() 非常基本,它不使用身份验证信息结构,只是设置指示是否收到密码的成员。

支持代理用户的插件必须将代理用户的名称(客户端用户应获得其权限的 MySQL 用户)返回给服务器。为此,插件必须将 info->authenticated_as 成员设置为代理用户名。有关代理的信息,请参见 代理用户第 4.4.9.4 节,“在身份验证插件中实现代理用户支持”

插件描述符的 generate_authentication_string 成员接受密码并从其生成密码哈希(摘要)

  • 前两个参数是指向输出缓冲区及其最大字节长度的指针。该函数应将密码哈希写入输出缓冲区并将长度重置为实际的哈希长度。

  • 后两个参数指示密码输入缓冲区及其字节长度。

  • 该函数返回 0 表示成功,返回 1 表示发生错误。

对于 auth_simple 插件,generate_auth_string_hash() 函数实现 generate_authentication_string 成员。它只是复制密码,除非它太长而无法放入输出缓冲区。

int generate_auth_string_hash(char *outbuf, unsigned int *buflen,
                              const char *inbuf, unsigned int inbuflen)
{
  /*
    fail if buffer specified by server cannot be copied to output buffer
  */
  if (*buflen < inbuflen)
    return 1;   /* error */
  strncpy(outbuf, inbuf, inbuflen);
  *buflen= strlen(inbuf);
  return 0;     /* success */
}

插件描述符的 validate_authentication_string 成员验证密码哈希

  • 参数是指向密码哈希及其字节长度的指针。

  • 该函数返回 0 表示成功,返回 1 表示无法验证密码哈希。

对于 auth_simple 插件,validate_auth_string_hash() 函数实现 validate_authentication_string 成员。它无条件地返回成功

int validate_auth_string_hash(char* const inbuf  __attribute__((unused)),
                              unsigned int buflen  __attribute__((unused)))
{
  return 0;     /* success */
}

插件描述符的 set_salt 成员仅由 mysql_native_password 插件使用(参见 原生可插拔身份验证)。对于其他身份验证插件,您可以使用此微不足道的实现

int set_salt(const char* password __attribute__((unused)),
             unsigned int password_len __attribute__((unused)),
             unsigned char* salt __attribute__((unused)),
             unsigned char* salt_len)
{
  *salt_len= 0;
  return 0;     /* success */
}

插件描述符的 authentication_flags 成员包含影响插件操作的标志。允许的标志是

  • AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE: 凭据更改是特权操作。如果设置了此标志,服务器要求用户具有全局 CREATE USER 权限或 mysql 数据库的 UPDATE 权限。

  • AUTH_FLAG_USES_INTERNAL_STORAGE: 插件是否使用内部存储(在 mysql.user 行的 authentication_string 列中)。如果未设置此标志,则尝试设置密码将失败,并且服务器会发出警告。

  • AUTH_FLAG_REQUIRES_REGISTRATION: 此标志为需要注册过程的认证插件设置。它在 CREATE USERALTER USER 语句中以及 authentication_policy 系统变量被分配值时进行检查。