擴展 MySQL 8.4  /  ...  /  在身份驗證插件中實作代理使用者支援

4.4.9.4 在身份驗證插件中實作代理使用者支援

可插拔身份驗證實現的功能之一是代理使用者(請參閱 代理使用者)。若要讓伺服器端身份驗證插件參與代理使用者支援,必須滿足以下條件

  • 當連線的客戶端應被視為代理使用者時,插件必須在 MYSQL_SERVER_AUTH_INFO 結構的 authenticated_as 成員中傳回不同的名稱,以指示被代理的使用者名稱。它也可以選擇性地設定 external_user 成員,以設定 external_user 系統變數的值。

  • 必須設定代理使用者帳戶,以便透過插件進行身份驗證。使用 CREATE USERGRANT 陳述式,將帳戶與插件相關聯。

  • 代理使用者帳戶必須具有被代理帳戶的 PROXY 權限。使用 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'

當伺服器叫用插件來驗證客戶端時,它會將適當的身份驗證字串傳遞給插件。插件負責

  1. 將字串解析為其組成部分,以確定要使用的對應

  2. 將客戶端使用者名稱與對應進行比較

  3. 傳回正確的 MySQL 使用者名稱

例如,如果 extuser2example.com 主機連線,伺服器會將 'extuser1=mysqlusera, extuser2=mysqluserb' 傳遞給插件,並且插件應將 mysqluserb 複製到 authenticated_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'