MySQL 服务器使用身份验证插件来认证客户端连接。认证插件可能会请求将连接的外部用户视为另一个用户,以便进行权限检查。这使得外部用户可以代理另一个用户,即假扮为另一个用户的身份:
-
外部用户是一个 “代理用户”(可以假扮为另一个用户的用户)。
-
第二个用户是一个 “被代理用户”(其身份和权限可以被代理用户假扮)。
本节描述了代理用户功能的工作原理。有关身份验证插件的一般信息,请参阅 第 8.2.17 节,“可插拔身份验证”。有关特定插件的信息,请参阅 第 8.4.1 节,“身份验证插件”。有关编写支持代理用户的身份验证插件的信息,请参阅 在身份验证插件中实现代理用户支持。
代理用户的一个管理优势是 DBA 可以设置一个账户具有特定的权限,然后启用多个代理用户拥有这些权限,而不需要单独授予每个用户这些权限。作为代理用户的替代方案,DBA 可以使用角色来映射用户到特定的权限集。每个用户可以被授予一个角色,以便获得适当的权限集。请参阅 第 8.2.10 节,“使用角色”。
要使代理用户功能生效,必须满足以下条件:
-
代理用户功能必须由插件本身或 MySQL 服务器支持。在后一种情况下,服务器支持可能需要显式启用;请参阅 服务器支持代理用户映射。
-
外部代理用户账户必须设置为由插件认证。使用
CREATE USER
语句将账户关联到身份验证插件,或者使用ALTER USER
语句更改插件。 -
被代理用户账户必须存在,并且被授予了要被代理用户假扮的权限。使用
CREATE USER
和GRANT
语句来实现。 -
通常,被代理用户被配置为只能在代理场景中使用,而不能用于直接登录。
-
当客户端连接到代理账户时,身份验证插件必须返回一个不同于客户端用户名的用户名,以指示代理用户的权限。
或者,对于服务器提供的代理映射的插件,代理用户是根据代理用户持有的
PROXY
权限确定的。
代理机制仅允许将外部客户端用户名映射到代理用户名,不允许映射主机名:
-
当客户端连接到服务器时,服务器根据客户端程序传递的用户名和客户端连接的主机确定适当的账户。
-
如果该账户是一个代理账户,服务器尝试根据身份验证插件返回的用户名和代理账户的主机名来确定适当的代理账户。代理账户中的主机名将被忽略。
考虑以下账户定义:
-- create proxy account
CREATE USER 'employee_ext'@'localhost'
IDENTIFIED WITH my_auth_plugin
AS 'my_auth_string';
-- create proxied account and grant its privileges;
-- use mysql_no_login plugin to prevent direct login
CREATE USER 'employee'@'localhost'
IDENTIFIED WITH mysql_no_login;
GRANT ALL
ON employees.*
TO 'employee'@'localhost';
-- grant to proxy account the
-- PROXY privilege for proxied account
GRANT PROXY
ON 'employee'@'localhost'
TO 'employee_ext'@'localhost';
当客户端以 employee_ext
身份从本地主机连接时,MySQL 使用名为 my_auth_plugin
的插件进行身份验证。假设 my_auth_plugin
根据 '
的内容和可能的外部身份验证系统返回用户名 my_auth_string
'employee
给服务器。由于 employee
与 employee_ext
不同,因此返回 employee
将作为服务器对 employee_ext
外部用户的请求,以便在权限检查期间将其视为 employee
本地用户。
在这种情况下,employee_ext
是代理用户,而 employee
是代理用户。
服务器通过检查 employee_ext
(代理用户)是否拥有 employee
(代理用户)的 PROXY
权限来验证代理身份验证是否可能。如果未授予该权限,将发生错误。否则,employee_ext
将假定 employee
的权限。服务器将在客户端会话期间执行的语句检查对 employee
授予的权限。在这种情况下,employee_ext
可以访问 employees
数据库中的表。
代理账户 employee
使用 mysql_no_login
身份验证插件来防止客户端直接登录。(这假设插件已安装。有关说明,请参阅第 8.4.1.9 节,“No-Login Pluggable Authentication”。)有关保护代理账户免受直接使用的替代方法,请参阅Preventing Direct Login to Proxied Accounts。
在代理发生时,USER()
和 CURRENT_USER()
函数可以用来查看连接用户(代理用户)和当前会话期间适用的账户(代理用户)之间的差异。对于上述示例,这些函数将返回以下值:
mysql> SELECT USER(), CURRENT_USER();
+------------------------+--------------------+
| USER() | CURRENT_USER() |
+------------------------+--------------------+
| employee_ext@localhost | employee@localhost |
+------------------------+--------------------+
在 CREATE USER
语句中创建代理用户账户时,IDENTIFIED WITH
子句指定了支持代理的身份验证插件,后跟可选的 AS '
子句,指定了服务器在用户连接时传递给插件的字符串。该字符串提供了帮助插件确定如何将代理(外部)客户端用户名映射到代理用户名的信息。它取决于每个插件是否需要 auth_string
'AS
子句。如果需要,身份验证字符串的格式取决于插件如何使用它。请参阅特定插件的文档,以获取有关身份验证字符串值的信息。
代理账户通常旨在仅通过代理账户使用,即客户端使用代理账户连接,然后映射到适当的代理用户的权限。
有多种方式确保代理账户不能直接使用:
-
将账户与
mysql_no_login
身份验证插件关联。在这种情况下,账户在任何情况下都不能用于直接登录。这假设插件已经安装。有关说明,请参阅 第 8.4.1.9 节,“无登录可插拔身份验证”。 -
在创建账户时包括
ACCOUNT LOCK
选项。请参阅 第 15.7.1.3 节,“CREATE USER 语句”。使用这种方法时,也包括密码,以便如果账户稍后解锁,不能使用无密码访问它。(如果启用了validate_password
组件,即使账户锁定,也不能创建没有密码的账户。请参阅 第 8.4.3 节,“密码验证组件”。) -
创建账户并设置密码,但不要告诉任何人密码。如果您不让任何人知道账户的密码,客户端就不能直接连接到 MySQL 服务器。
需要 PROXY
权限来启用外部用户作为另一个用户连接并拥有其权限。要授予此权限,请使用 GRANT
语句。例如:
GRANT PROXY ON 'proxied_user' TO 'proxy_user';
该语句在 mysql.proxies_priv
授予权表中创建一行。
在连接时,proxy_user
必须表示有效的外部认证 MySQL 用户,而 proxied_user
必须表示有效的本地认证用户。否则,连接尝试将失败。
相应的 REVOKE
语法为:
REVOKE PROXY ON 'proxied_user' FROM 'proxy_user';
MySQL GRANT
和 REVOKE
语法扩展按常规工作。示例:
-- grant PROXY to multiple accounts
GRANT PROXY ON 'a' TO 'b', 'c', 'd';
-- revoke PROXY from multiple accounts
REVOKE PROXY ON 'a' FROM 'b', 'c', 'd';
-- grant PROXY to an account and enable the account to grant
-- PROXY to the proxied account
GRANT PROXY ON 'a' TO 'd' WITH GRANT OPTION;
-- grant PROXY to default proxy account
GRANT PROXY ON 'a' TO ''@'';
可以在以下情况下授予 PROXY
权限:
-
由拥有
GRANT PROXY ... WITH GRANT OPTION
的用户授予proxied_user
。 -
由
proxied_user
自己授予:USER()
的值必须与CURRENT_USER()
和proxied_user
的用户名和主机名部分完全匹配。
在 MySQL 安装期间创建的初始 root
账户拥有 PROXY ... WITH GRANT OPTION
权限,用于所有用户和所有主机。这使得 root
能够设置代理用户,以及将权限委托给其他账户来设置代理用户。例如,root
可以这样做:
CREATE USER 'admin'@'localhost'
IDENTIFIED BY 'admin_password';
GRANT PROXY
ON ''@''
TO 'admin'@'localhost'
WITH GRANT OPTION;
这些语句创建了一个 admin
用户,该用户可以管理所有 GRANT PROXY
映射。例如,admin
可以这样做:
GRANT PROXY ON sally TO joe;
要指定某些或所有用户应使用给定的身份验证插件连接,创建一个空的 MySQL 账户(''@''
),将其与该插件关联,并让插件返回实际认证的用户名(如果不同于空用户)。假设存在一个名为 ldap_auth
的插件,它实现 LDAP 认证并将连接用户映射到开发者或经理账户。要设置代理用户到这些账户,请使用以下语句:
-- create default proxy account
CREATE USER ''@''
IDENTIFIED WITH ldap_auth
AS 'O=Oracle, OU=MySQL';
-- create proxied accounts; use
-- mysql_no_login plugin to prevent direct login
CREATE USER 'developer'@'localhost'
IDENTIFIED WITH mysql_no_login;
CREATE USER 'manager'@'localhost'
IDENTIFIED WITH mysql_no_login;
-- grant to default proxy account the
-- PROXY privilege for proxied accounts
GRANT PROXY
ON 'manager'@'localhost'
TO ''@'';
GRANT PROXY
ON 'developer'@'localhost'
TO ''@'';
现在假设客户端连接如下:
$> mysql --user=myuser --password ...
Enter password: myuser_password
服务器不找到定义为 MySQL 用户的 myuser
,但因为存在一个空白用户账户 (''@''
),该账户与客户端用户名和主机名匹配,服务器将客户端身份验证到该账户。服务器调用 ldap_auth
身份验证插件,并将 myuser
和 myuser_password
作为用户名和密码传递给它。
如果 ldap_auth
插件在 LDAP 目录中发现 myuser_password
不是 myuser
的正确密码,身份验证失败,服务器拒绝连接。
如果密码正确,ldap_auth
发现 myuser
是开发者,它将用户名 developer
返回到 MySQL 服务器,而不是 myuser
。返回与客户端用户名 myuser
不同的用户名,表明服务器应该将 myuser
视为代理。服务器验证 ''@''
是否可以作为 developer
进行身份验证(因为 ''@''
具有 PROXY
权限),并接受连接。会话继续以 myuser
具有 developer
代理用户的权限。(这些权限应该由 DBA 使用 GRANT
语句设置,不显示。)USER()
和 CURRENT_USER()
函数返回这些值:
mysql> SELECT USER(), CURRENT_USER();
+------------------+---------------------+
| USER() | CURRENT_USER() |
+------------------+---------------------+
| myuser@localhost | developer@localhost |
+------------------+---------------------+
如果插件在 LDAP 目录中发现 myuser
是经理,它将用户名 manager
返回,并且会话继续以 myuser
具有 manager
代理用户的权限。
mysql> SELECT USER(), CURRENT_USER();
+------------------+-------------------+
| USER() | CURRENT_USER() |
+------------------+-------------------+
| myuser@localhost | manager@localhost |
+------------------+-------------------+
出于简洁性,外部身份验证不能是多级的:既不考虑 developer
的凭据,也不考虑 manager
的凭据。但是,它们仍然用于客户端尝试直接连接和身份验证为 developer
或 manager
账户,这就是为什么这些代理账户应该受到保护,以免直接登录(见 Preventing Direct Login to Proxied Accounts)。
如果您打算创建默认代理用户,请检查其他现有的 “匹配任何用户” 账户,这些账户可能会阻止默认代理用户正常工作。
在前面的讨论中,默认代理用户账户的主机部分是 ''
,它匹配任何主机。如果您设置默认代理用户,请务必检查是否存在其他非代理账户,具有相同的用户部分和 '%'
在主机部分,因为 '%'
也匹配任何主机,但根据服务器用于内部排序账户行的规则,它具有比 ''
更高的优先级(见 第 8.2.6 节,“访问控制,阶段 1:连接验证”)。
假设 MySQL 安装包括以下两个账户:
-- create default proxy account
CREATE USER ''@''
IDENTIFIED WITH some_plugin
AS 'some_auth_string';
-- create anonymous account
CREATE USER ''@'%'
IDENTIFIED BY 'anon_user_password';
第一个账户 (''@''
) 是默认代理用户,用于身份验证连接到没有其他账户匹配的用户。第二个账户 (''@'%'
) 是匿名用户账户,可能是为了启用用户无需自己的账户连接匿名地连接而创建的。
这两个账户都具有相同的用户部分 (''
),它匹配任何用户。每个账户都有一个主机部分,匹配任何主机。然而,在连接尝试时,账户匹配规则具有优先级,因为 '%'
在主机部分的优先级高于 ''
。因此,对于不匹配任何其他账户的账户,服务器将尝试身份验证到 ''@'%'
(匿名用户)而不是 ''@''
(默认代理用户)。结果,默认代理账户从不使用。
要避免这个问题,可以使用以下策略:
-
删除匿名账户,以便它不与默认代理用户冲突。
-
使用一个更具体的默认代理用户,该用户在匿名用户之前匹配。例如,要允许仅来自
localhost
的代理连接,请使用''@'localhost'
:CREATE USER ''@'localhost' IDENTIFIED WITH some_plugin AS 'some_auth_string';
此外,修改任何
GRANT PROXY
语句,以便将代理用户命名为''@'localhost'
而不是''@''
。请注意,这种策略将阻止来自
localhost
的匿名用户连接。 -
使用命名的默认帐户,而不是匿名的默认帐户。例如, consult the instructions for using the
authentication_windows
插件。见 第 8.4.1.6 节,“Windows 可插拔身份验证”。 -
创建多个代理用户,一个用于本地连接,一个用于 “其他所有”(远程连接)。这特别有用,当本地用户应该具有与远程用户不同的权限时。
创建代理用户:
-- create proxy user for local connections CREATE USER ''@'localhost' IDENTIFIED WITH some_plugin AS 'some_auth_string'; -- create proxy user for remote connections CREATE USER ''@'%' IDENTIFIED WITH some_plugin AS 'some_auth_string';
创建被代理用户:
-- create proxied user for local connections CREATE USER 'developer'@'localhost' IDENTIFIED WITH mysql_no_login; -- create proxied user for remote connections CREATE USER 'developer'@'%' IDENTIFIED WITH mysql_no_login;
授予每个代理帐户对应的被代理帐户的
PROXY
权限:GRANT PROXY ON 'developer'@'localhost' TO ''@'localhost'; GRANT PROXY ON 'developer'@'%' TO ''@'%';
最后,授予本地和远程被代理用户适当的权限(未显示)。
假设
some_plugin
/'
组合导致some_auth_string
'some_plugin
将客户端用户名映射到developer
。本地连接匹配''@'localhost'
代理用户,该用户映射到'developer'@'localhost'
被代理用户。远程连接匹配''@'%'
代理用户,该用户映射到'developer'@'%'
被代理用户。
一些身份验证插件自己实现代理用户映射(例如,PAM 和 Windows 身份验证插件)。其他身份验证插件不支持代理用户默认情况下。这些插件中的一些可以请求 MySQL 服务器自己根据授予的代理权限映射代理用户:mysql_native_password
、sha256_password
。如果 check_proxy_users
系统变量启用,服务器将为任何请求服务器支持代理用户的身份验证插件执行代理用户映射:
-
默认情况下,
check_proxy_users
禁用,因此服务器即使对于请求服务器支持代理用户的身份验证插件,也不执行代理用户映射。 -
如果
check_proxy_users
启用,也可能需要启用插件特定的系统变量,以便利用服务器的代理用户映射支持:-
对于已弃用的
mysql_native_password
插件,启用mysql_native_password_proxy_users
。 -
对于
sha256_password
插件,启用sha256_password_proxy_users
。
-
例如,要启用所有这些功能,请在 my.cnf
文件中添加以下行:
[mysqld]
check_proxy_users=ON
mysql_native_password_proxy_users=ON
sha256_password_proxy_users=ON
假设相关的系统变量已经启用,创建代理用户,如通常使用 CREATE USER
,然后授予它对单个其他帐户的 PROXY
权限,以便将其视为被代理用户。当服务器收到成功的连接请求时,它会发现用户具有 PROXY
权限,并使用它来确定适当的被代理用户。
-- create proxy account
CREATE USER 'proxy_user'@'localhost'
IDENTIFIED WITH mysql_native_password
BY 'password';
-- create proxied account and grant its privileges;
-- use mysql_no_login plugin to prevent direct login
CREATE USER 'proxied_user'@'localhost'
IDENTIFIED WITH mysql_no_login;
-- grant privileges to proxied account
GRANT ...
ON ...
TO 'proxied_user'@'localhost';
-- grant to proxy account the
-- PROXY privilege for proxied account
GRANT PROXY
ON 'proxied_user'@'localhost'
TO 'proxy_user'@'localhost';
要使用代理帐户,请使用其名称和密码连接到服务器:
$> mysql -u proxy_user -p
Enter password: (enter proxy_user password here)
身份验证成功,服务器发现 proxy_user
具有 PROXY
权限 для proxied_user
,并且会话继续以 proxy_user
具有的 proxied_user
权限。
服务器执行的代理用户映射受以下限制:
-
服务器不代理匿名用户,即使关联的
PROXY
权限被授予。 -
当单个帐户被授予了多个代理帐户的代理权限时,服务器代理用户映射将是非确定性的。因此,不鼓励授予单个帐户多个代理帐户的代理权限。
两个系统变量有助于跟踪代理登录过程:
-
proxy_user
:如果不使用代理,则该值为NULL
。否则,它将指示代理用户帐户。例如,如果客户端通过''@''
代理帐户进行身份验证,则该变量将设置如下:mysql> SELECT @@proxy_user; +--------------+ | @@proxy_user | +--------------+ | ''@'' | +--------------+
-
external_user
:有时身份验证插件可能使用外部用户来身份验证到 MySQL 服务器。例如,在使用 Windows 本机身份验证时,使用 Windows API 进行身份验证的插件不需要将登录 ID 传递给它。但是,它仍然使用 Windows 用户 ID 进行身份验证。插件可能将外部用户 ID(或其前 512 个 UTF-8 字节)返回到服务器,使用external_user
只读会话变量。如果插件不设置该变量,其值将为NULL
。