8.2.19 代理用户
MySQL 服务器使用身份验证插件来验证客户端连接。验证插件可能会要求连接的外部用户被视为另一个用户,以便为权限检查目的使用该用户。这使得外部用户可以作为第二个用户的代理,以假设第二个用户的权限:
-
外部用户被称为“代理用户”(可以模拟或被认为是另一个用户)。
-
第二个用户被称为“被代理用户”(可以被代理用户模拟或被认为是另一个用户)。
本节描述了代理用户机制的工作原理。关于身份验证插件的总体信息,请见第8.2.17节,“可插拔身份验证”。关于特定插件的信息,请见第8.4.1节,“身份验证插件”。关于编写支持代理用户的身份验证插件,请见在身份验证插件中实现代理用户支持。
代理用户的一个管理益处是DBA可以设置一个帐户的权限,然后启用多个代理用户拥有这些权限,而不需要为每个用户分配权限。DBA可能会发现,角色提供了将用户映射到特定权限集的方式。每个用户可以被授予单个角色,以便授予适当的权限。请见第8.2.10节,“使用角色”。
为了使代理用户在给定的身份验证插件中生效,必须满足以下条件:
-
代理必须被支持,通过插件本身或MySQL服务器在插件的 behalf。后一种情况,服务器支持可能需要被启用;请参见服务器支持代理用户映射。
-
外部代理用户的账户必须被设置为由插件进行身份验证。使用
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
将用户名返回为employee
,基于'
的内容或外部身份验证系统的内容。名称my_auth_string
'employee
不同于employee_ext
,因此返回employee
作为请求服务器将employee_ext
外部用户视为employee
本地用户,以便在权限检查时进行。
在这种情况下,employee_ext
是代理用户,而 employee
是被代理用户。
服务器验证 employee_ext
用户是否可以为 employee
用户执行代理身份验证,方法是检查 employee_ext
用户是否拥有 PROXY
权限,以便访问 employee
用户。如果没有授予权限,会出现错误。否则,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';
MySQLGRANT
和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
自己授予:The value ofUSER()
must exactly matchCURRENT_USER()
andproxied_user
, for both the user name and host name parts of the account name.
安装 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
服务器找不到 myuser
定义为 MySQL 用户,但因为存在一个空白用户账户(''@''
),该账户匹配客户端用户名和主机名,服务器对客户端进行身份验证。服务器调用 ldap_auth
身份验证插件,并将 myuser
和 myuser_password
传递给它作为用户名和密码。
如果 ldap_auth
插件在 LDAP 目录中找到 myuser_password
不是 myuser
的正确密码,身份验证失败,服务器拒绝连接。
如果密码正确,ldap_auth
插件找到 myuser
是开发者,它返回用户名 developer
给 MySQL 服务器,而不是 myuser
。返回的用户名不同于客户端用户名 myuser
,提示服务器将 myuser
视为代理用户。服务器验证 ''@''
可以作为 developer
进行身份验证(因为 ''@''
拥有PROXY
特权),并接受连接。会话继续使用 myuser
拥有代理用户的权限。 (这些权限应该由 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)。
如果你想创建默认代理用户,请检查其他存在的“match any user”账户,因为它们可以防止默认代理用户工作正常。
在前面的讨论中,默认代理用户账户的主机部分是''
,这将匹配任何主机。如果你设置了默认代理用户,请确保也检查是否存在同名账户,但主机部分是'%'
,因为'%'
也可以匹配任何主机,但根据服务器内部排序规则,它们的优先级高于''
(参见Section 8.2.6, “Access Control, Stage 1: Connection Verification”)。
假设有一台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
匿名用户连接。 -
使用命名默认账户,而不是匿名默认账户。关于该技术的示例,请参阅第8.4.1.6节,“Windows Pluggable Authentication”。
-
创建多个代理用户,一個用于本地连接,一個用于“everything else”(远程连接)。这在本地用户和远程用户应该有不同的权限时非常有用。
创建代理用户:
-- 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(或其 UTF-8 字节的前 512 字节)返回到服务器使用external_user
只读会话变量。如果插件不设置这个变量,它的值是NULL
。