延伸 MySQL 9.0  /  ...  /  編寫伺服器端身份驗證插件

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 的值)。插件可以變更它以指出用戶端應具有不同用戶的權限。例如,如果插件支援 Proxy User,則初始值是連線(Proxy)用戶的名稱,而插件可以將此成員變更為 Proxy User 的名稱。然後,伺服器會將 Proxy User 視為擁有 Proxy User 的權限(假設滿足 Proxy User 支援的其他條件;請參閱第 4.4.9.4 節「在身份驗證插件中實作 Proxy User 支援」)。該值表示為最多 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 原始檔。

伺服器會計算 Performance Schema 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 外掛程式使用,該外掛程式在 MySQL 9.0 中已不再支援。對於其他身份驗證外掛程式,您可以使用這個簡單的實作。

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 系統變數被賦值時,會檢查此旗標。