MySQL8 中文参考(二十五)
8.2.11 账户类别
从 MySQL 8.0.16 开始,MySQL 引入了基于 SYSTEM_USER 权限的用户账户类别概念。
-
系统和常规账户
-
SYSTEM_USER 权限影响的操作
-
系统和常规会话
-
保护系统账户免受常规账户篡改
系统和常规账户
MySQL 引入了用户账户类别的概念,根据是否拥有 SYSTEM_USER 权限来区分系统用户和常规用户:
-
拥有
SYSTEM_USER权限的用户是系统用户。 -
没有
SYSTEM_USER权限的用户是常规用户。
SYSTEM_USER 权限对用户可以应用其它权限的账户产生影响,以及用户是否受到其他账户的保护:
-
系统用户可以修改系统和常规账户。也就是说,拥有适当权限在常规账户上执行特定操作的用户,通过拥有
SYSTEM_USER权限也能在系统账户上执行该操作。系统账户只能被拥有适当权限的系统用户修改,而不能被常规用户修改。 -
拥有适当权限的常规用户可以修改常规账户,但不能修改系统账户。系统账户可以被拥有适当权限的系统用户和常规用户修改。
如果用户有适当权限在常规账户上执行特定操作,SYSTEM_USER 使用户也能在系统账户上执行该操作。SYSTEM_USER 不意味着任何其他权限,因此执行特定账户操作的能力仍取决于是否拥有任何其他所需权限。例如,如果用户可以授予常规账户 SELECT 和 UPDATE 权限,那么拥有 SYSTEM_USER 的用户也可以授予系统账户 SELECT 和 UPDATE 权限。
系统账户和普通账户之间的区别通过保护具有SYSTEM_USER特权的账户免受没有该特权的账户的影响,从而更好地控制某些账户管理问题。例如,CREATE USER特权不仅允许创建新账户,还允许修改和删除现有账户。没有系统用户概念,拥有CREATE USER特权的用户可以修改或删除任何现有账户,包括root账户。系统用户概念使得限制对root账户(本身是系统账户)的修改只能由系统用户进行。拥有CREATE USER特权的普通用户仍然可以修改或删除现有账户,但只能是普通账户。
受SYSTEM_USER特权影响的操作
SYSTEM_USER特权影响以下操作:
-
账户操作。
账户操作包括创建和删除账户,授予和撤销特权,更改账户认证特性(如凭证或认证插件),以及更改其他账户特性,如密码过期策略。
使用账户管理语句(如
CREATE USER和GRANT")操纵系统账户需要SYSTEM_USER特权。为防止账户以这种方式修改系统账户,将其设为普通账户,不授予SYSTEM_USER特权。(然而,要完全保护系统账户免受普通账户的修改,还必须对普通账户限制对mysql系统模式的修改特权。请参见保护系统账户免受普通账户操纵。) -
终止当前会话和其中执行的语句。
要终止使用
SYSTEM_USER特权执行的会话或语句,您自己的会话必须具有SYSTEM_USER特权,以及任何其他所需特权(CONNECTION_ADMIN或已弃用的SUPER特权)。从 MySQL 8.0.30 开始,如果将服务器置于脱机模式的用户没有
SYSTEM_USER权限,则具有SYSTEM_USER权限的连接客户端用户也不会被断开连接。但是,这些用户在服务器处于脱机模式时无法启动新连接,除非他们还具有CONNECTION_ADMIN或SUPER权限。仅仅是他们现有的连接不会被终止,因为需要SYSTEM_USER权限才能执行该操作。在 MySQL 8.0.16 之前,
CONNECTION_ADMIN权限(或已弃用的SUPER权限)足以终止任何会话或语句。 -
为存储对象设置
DEFINER属性。要将存储对象的
DEFINER属性设置为具有SYSTEM_USER权限的帐户,您必须具有SYSTEM_USER权限,以及任何其他所需权限(SET_USER_ID或已弃用的SUPER权限)。在 MySQL 8.0.16 之前,
SET_USER_ID权限(或已弃用的SUPER权限)足以为存储对象指定任何DEFINER值。 -
指定强制角色。
具有
SYSTEM_USER权限的角色不能在mandatory_roles系统变量的值中列出。在 MySQL 8.0.16 之前,任何角色都可以列在
mandatory_roles中。 -
覆盖 MySQL Enterprise Audit 的审计日志过滤器中的“中止”项目。
从 MySQL 8.0.28 开始,具有
SYSTEM_USER权限的帐户会自动被分配AUDIT_ABORT_EXEMPT权限,因此即使审计日志过滤器中的“中止”项目会阻止它们,该帐户的查询也会始终执行。具有SYSTEM_USER权限的帐户因此可用于在审计配置错误后恢复对系统的访问。参见第 8.4.5 节,“MySQL Enterprise Audit”。
系统和常规会话
在服务器内执行的会��被区分为系统会话或常规会话,类似于系统和常规用户之间的区别:
-
拥有
SYSTEM_USER权限的会话是系统会话。 -
不具有
SYSTEM_USER权限的会话是普通会话。
普通会话只能执行普通用户允许的操作。系统会话还能执行仅允许系统用户的操作。
会话拥有的权限包括直接授予其基础帐户的权限,以及授予会话内所有当前活动角色的权限。因此,会话可能是系统会话,因为其帐户直接被授予SYSTEM_USER权限,或者因为会话已激活具有SYSTEM_USER权限的角色。授予帐户但在会话中未激活的角色不会影响会话权限。
因为激活和停用角色可以改变会话拥有的权限,会话可能从普通会话变为系统会话,反之亦然。如果会话激活或停用具有SYSTEM_USER权限的角色,则会话之间的普通和系统会话之间的适当变化立即发生,仅适用于该会话:
-
如果普通会话激活具有
SYSTEM_USER权限的角色,则该会话将变为系统会话。 -
如果系统会话停用具有
SYSTEM_USER权限的角色,则该会话将变为普通会话,除非仍有其他具有SYSTEM_USER权限的角色处于活动状态。
这些操作不会影响现有会话:
-
如果向帐户授予或撤销
SYSTEM_USER权限,则帐户的现有会话在普通会话和系统会话之间不会发生变化。授予或撤销操作仅影响帐户后续连接的会话。 -
在会话中调用的存储对象执行的语句将以父会话的系统或普通状态执行,即使对象的
DEFINER属性命名了系统帐户。
因为角色激活仅影响会话而不影响帐户,向普通帐户授予具有SYSTEM_USER权限的角色不会保护该帐户免受普通用户的攻击。该角色仅保护已激活该角色的帐户的会话,并仅保护会话免受普通会话的终止。
保护系统帐户免受普通帐户的操纵
帐户操作包括创建和删除帐户,授予和撤销权限,更改帐户身份验证特性,如凭据或身份验证插件,以及更改其他帐户特性,如密码过期策略。
帐户操作可以通过两种方式完成:
-
通过使用诸如
CREATE USER和GRANT等帐户管理语句。这是首选方法。 -
通过使用诸如
INSERT和UPDATE等语句直接修改授权表。虽然不鼓励这种方法,但对于具有mysql系统模式上适当特权的用户是可能的。
要完全保护系统帐户免受特定帐户的修改,将其设置为常规帐户,并不授予mysql模式的修改特权:
-
使用帐户管理语句操作系统帐户需要
SYSTEM_USER特权。为防止帐户以这种方式修改系统帐户,将其设置为常规帐户,不授予SYSTEM_USER。这包括不授予任何角色授予的帐户SYSTEM_USER。 -
mysql模式的特权允许通过直接修改授权表来操作系统帐户,即使修改帐户是常规帐户。为了防止常规帐户通过直接修改未经授权地修改系统帐户,请不要向帐户(或任何授予帐户的角色)授予mysql模式的修改特权。如果常规帐户必须具有适用于所有模式的全局特权,则可以使用部分撤销施加的特权限制来阻止mysql模式的修改。参见 Section 8.2.12, “Privilege Restriction Using Partial Revokes”。
注意
与不授予SYSTEM_USER特权不同,阻止帐户修改系统帐户但不是常规帐户,不授予mysql模式特权会阻止帐户修改系统帐户以及常规帐户。这不应该是一个问题,因为如前所述,不鼓励直接修改授权表。
假设您想要创建一个名为u1的用户,该用户对所有模式具有所有特权,但u1应该是一个不能修改系统帐户的常规用户。假设partial_revokes系统变量已启用,配置u1如下:
CREATE USER u1 IDENTIFIED BY '*password*';
GRANT ALL ON *.* TO u1 WITH GRANT OPTION;
-- GRANT ALL includes SYSTEM_USER, so at this point
-- u1 can manipulate system or regular accounts
REVOKE SYSTEM_USER ON *.* FROM u1;
-- Revoking SYSTEM_USER makes u1 a regular user;
-- now u1 can use account-management statements
-- to manipulate only regular accounts
REVOKE ALL ON mysql.* FROM u1;
-- This partial revoke prevents u1 from directly
-- modifying grant tables to manipulate accounts
为防止帐户访问所有mysql系统模式,撤销其在mysql模式上的所有特权,如上所示。也可以允许部分mysql模式访问,例如只读访问。以下示例创建一个帐户,该帐户对所有模式具有全局的SELECT、INSERT、UPDATE和DELETE特权,但对mysql模式仅具有SELECT特权:
CREATE USER u2 IDENTIFIED BY '*password*';
GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO u2;
REVOKE INSERT, UPDATE, DELETE ON mysql.* FROM u2;
另一种可能性是撤销所有mysql模式的权限,但授予对特定mysql表或列的访问权限。即使对mysql进行部分撤销,也可以完成此操作。以下语句允许在mysql模式内对u1进行只读访问,但仅限于db表以及user表的Host和User列:
CREATE USER u3 IDENTIFIED BY '*password*';
GRANT ALL ON *.* TO u3;
REVOKE ALL ON mysql.* FROM u3;
GRANT SELECT ON mysql.db TO u3;
GRANT SELECT(Host,User) ON mysql.user TO u3;
8.2.12 使用部分撤销进行权限限制
在 MySQL 8.0.16 之前,不可能授予全局适用的权限,除了某些模式。从 MySQL 8.0.16 开始,如果启用了partial_revokes系统变量,则可以实现。具体来说,对于在全局级别具有权限的用户,partial_revokes使得可以撤销特定模式的权限,同时保留其他模式的权限。因此,施加的权限限制可能对具有全局权限但不应被允许访问某些模式的账户的管理很有用。例如,可以允许一个账户修改任何表,除了mysql系统模式中的表。
-
使用部分撤销
-
部分撤销与显式模式授权
-
禁用部分撤销
-
部分撤销和复制
注意
为简洁起见,此处显示的CREATE USER语句不包括密码。在生产环境中,请始终分配账户密码。
使用部分撤销
partial_revokes系统变量控制是否可以对账户施加权限限制。默认情况下,partial_revokes被禁用,尝试部分撤销全局权限会产生错误:
mysql> CREATE USER u1;
mysql> GRANT SELECT, INSERT ON *.* TO u1;
mysql> REVOKE INSERT ON world.* FROM u1;
ERROR 1141 (42000): There is no such grant defined for user 'u1' on host '%'
要允许REVOKE操作,请启用partial_revokes:
SET PERSIST partial_revokes = ON;
SET PERSIST为运行中的 MySQL 实例设置一个值。它还保存该值,使其在后续服务器重启时保留。要更改运行中的 MySQL 实例的值,而不使其在后续重启时保留,使用GLOBAL关键字而不是PERSIST。参见 Section 15.7.6.1, “SET Syntax for Variable Assignment”。
启用partial_revokes后,部分撤销成功:
mysql> REVOKE INSERT ON world.* FROM u1;
mysql> SHOW GRANTS FOR u1;
+------------------------------------------+
| Grants for u1@% |
+------------------------------------------+
| GRANT SELECT, INSERT ON *.* TO `u1`@`%` |
| REVOKE INSERT ON `world`.* FROM `u1`@`%` |
+------------------------------------------+
SHOW GRANTS在其输出中将部分撤销列为REVOKE语句。结果表明u1具有全局SELECT和INSERT权限,只是INSERT无法用于world模式中的表。也就是说,u1对world表的访问是只读的。
服务器记录通过部分撤销在mysql.user系统表中实施的权限限制。如果一个账户有部分撤销,其User_attributes列的值具有Restrictions属性:
mysql> SELECT User, Host, User_attributes->>'$.Restrictions'
FROM mysql.user WHERE User_attributes->>'$.Restrictions' <> '';
+------+------+------------------------------------------------------+
| User | Host | User_attributes->>'$.Restrictions' |
+------+------+------------------------------------------------------+
| u1 | % | [{"Database": "world", "Privileges": ["INSERT"]}] |
+------+------+------------------------------------------------------+
注意
尽管可以对任何模式施加部分撤销,但对mysql系统模式的权限限制特别有用,作为防止常规账户修改系统账户的策略的一部分。请参阅防止常规账户操纵系统账户。
部分撤销操作受到以下条件的约束:
-
可以使用部分撤销对不存在的模式施加限制,但只有在撤销的权限是全局授予的情况下才可以。如果权限没有全局授予,为不存在的模式撤销它会产生错误。
-
部分撤销仅适用于模式级。您不能对仅全局适用的权限(如
FILE或BINLOG_ADMIN)或表、列或例程权限使用部分撤销。 -
在权限分配中,启用
partial_revokes会导致 MySQL 将模式名称中未转义的_和%SQL 通配符字符解释为文字字符,就好像它们已经被转义为\_和\%一样。因为这会改变 MySQL 解释权限的方式,建议在启用partial_revokes的安装中避免未转义的通配符字符在权限分配中出现。
如前所述,模式级权限的部分撤销在SHOW GRANTS输出中显示为REVOKE语句。这与SHOW GRANTS表示“普通”模式级权限的方式不同:
-
当授予时,模式级权限通过其自己的
GRANT语句在输出中表示:mysql> CREATE USER u1; mysql> GRANT UPDATE ON mysql.* TO u1; mysql> GRANT DELETE ON world.* TO u1; mysql> SHOW GRANTS FOR u1; +---------------------------------------+ | Grants for u1@% | +---------------------------------------+ | GRANT USAGE ON *.* TO `u1`@`%` | | GRANT UPDATE ON `mysql`.* TO `u1`@`%` | | GRANT DELETE ON `world`.* TO `u1`@`%` | +---------------------------------------+ -
当撤销时,模式级权限会简单地从输出中消失。它们不会显示为
REVOKE语句:mysql> REVOKE UPDATE ON mysql.* FROM u1; mysql> REVOKE DELETE ON world.* FROM u1; mysql> SHOW GRANTS FOR u1; +--------------------------------+ | Grants for u1@% | +--------------------------------+ | GRANT USAGE ON *.* TO `u1`@`%` | +--------------------------------+
当用户授予特权时,授予者对特权的任何限制都会被受让者继承,除非受让者已经拥有该特权而没有限制。考虑以下两个用户,其中一个拥有全局SELECT特权:
CREATE USER u1, u2;
GRANT SELECT ON *.* TO u2;
假设一个管理用户admin有一个全局但部分撤销的SELECT特权:
mysql> CREATE USER admin;
mysql> GRANT SELECT ON *.* TO admin WITH GRANT OPTION;
mysql> REVOKE SELECT ON mysql.* FROM admin;
mysql> SHOW GRANTS FOR admin;
+------------------------------------------------------+
| Grants for admin@% |
+------------------------------------------------------+
| GRANT SELECT ON *.* TO `admin`@`%` WITH GRANT OPTION |
| REVOKE SELECT ON `mysql`.* FROM `admin`@`%` |
+------------------------------------------------------+
如果admin全局授予u1和u2``SELECT,则每个用户的结果不同:
-
如果
admin全局授予u1``SELECT,而u1一开始没有SELECT特权,u1会继承admin的特权限制:mysql> GRANT SELECT ON *.* TO u1; mysql> SHOW GRANTS FOR u1; +------------------------------------------+ | Grants for u1@% | +------------------------------------------+ | GRANT SELECT ON *.* TO `u1`@`%` | | REVOKE SELECT ON `mysql`.* FROM `u1`@`%` | +------------------------------------------+ -
另一方面,
u2已经拥有全局SELECT特权而没有限制。GRANT只能添加到受让者的现有特权,而不能减少它们,因此如果admin全局授予u2``SELECT,u2不会继承admin的限制:mysql> GRANT SELECT ON *.* TO u2; mysql> SHOW GRANTS FOR u2; +---------------------------------+ | Grants for u2@% | +---------------------------------+ | GRANT SELECT ON *.* TO `u2`@`%` | +---------------------------------+
如果GRANT语句包括一个AS *user*子句,应用的特权限制是子句指定的用户/角色组合上的,而不是执行该语句的用户上的。有关AS子句的信息,请参见 Section 15.7.1.6, “GRANT Statement”。
授予给账户的新特权的限制将添加到该账户的任何现有限制中:
mysql> CREATE USER u1;
mysql> GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO u1;
mysql> REVOKE INSERT ON mysql.* FROM u1;
mysql> SHOW GRANTS FOR u1;
+---------------------------------------------------------+
| Grants for u1@% |
+---------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO `u1`@`%` |
| REVOKE INSERT ON `mysql`.* FROM `u1`@`%` |
+---------------------------------------------------------+
mysql> REVOKE DELETE, UPDATE ON db2.* FROM u1;
mysql> SHOW GRANTS FOR u1;
+---------------------------------------------------------+
| Grants for u1@% |
+---------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO `u1`@`%` |
| REVOKE UPDATE, DELETE ON `db2`.* FROM `u1`@`%` |
| REVOKE INSERT ON `mysql`.* FROM `u1`@`%` |
+---------------------------------------------------------+
特权限制的聚合既适用于显式部分撤销特权(如刚刚显示的)又适用于从执行语句的用户或在AS *user*子句中提到的用户隐式继承限制时。
如果账户在模式上有特权限制:
-
该账户无法向其他账户授予对受限模式或其中任何对象的特权。
-
另一个没有限制的账户可以向受限账户授予受限模式或其中对象的特权。假设一个无限制用户执行以下语句:
CREATE USER u1; GRANT SELECT, INSERT, UPDATE ON *.* TO u1; REVOKE SELECT, INSERT, UPDATE ON mysql.* FROM u1; GRANT SELECT ON mysql.user TO u1; -- grant table privilege GRANT SELECT(Host,User) ON mysql.db TO u1; -- grant column privileges结果账户具有这些特权,能够在受限模式内执行有限操作:
mysql> SHOW GRANTS FOR u1; +-----------------------------------------------------------+ | Grants for u1@% | +-----------------------------------------------------------+ | GRANT SELECT, INSERT, UPDATE ON *.* TO `u1`@`%` | | REVOKE SELECT, INSERT, UPDATE ON `mysql`.* FROM `u1`@`%` | | GRANT SELECT (`Host`, `User`) ON `mysql`.`db` TO `u1`@`%` | | GRANT SELECT ON `mysql`.`user` TO `u1`@`%` | +-----------------------------------------------------------+
如果账户对全局特权有限制,这些操作中的任何一个都会移除限制:
-
由没有对特权限制的账户全局授予账户特权。
-
在模式级别授予特权。
-
全局撤销特权。
考虑一个全局拥有多个特权但对INSERT、UPDATE和DELETE有限制的用户u1:
mysql> CREATE USER u1;
mysql> GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO u1;
mysql> REVOKE INSERT, UPDATE, DELETE ON mysql.* FROM u1;
mysql> SHOW GRANTS FOR u1;
+----------------------------------------------------------+
| Grants for u1@% |
+----------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO `u1`@`%` |
| REVOKE INSERT, UPDATE, DELETE ON `mysql`.* FROM `u1`@`%` |
+----------------------------------------------------------+
从没有限制的帐户全局授予u1权限会移除权限限制。例如,要移除INSERT限制:
mysql> GRANT INSERT ON *.* TO u1;
mysql> SHOW GRANTS FOR u1;
+---------------------------------------------------------+
| Grants for u1@% |
+---------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO `u1`@`%` |
| REVOKE UPDATE, DELETE ON `mysql`.* FROM `u1`@`%` |
+---------------------------------------------------------+
在模式级别向u1授予权限会移除权限限制。例如,要移除UPDATE限制:
mysql> GRANT UPDATE ON mysql.* TO u1;
mysql> SHOW GRANTS FOR u1;
+---------------------------------------------------------+
| Grants for u1@% |
+---------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO `u1`@`%` |
| REVOKE DELETE ON `mysql`.* FROM `u1`@`%` |
+---------------------------------------------------------+
撤销全局特权会移除该特权,包括任何对其的限制。例如,要移除DELETE限制(以牺牲所有DELETE访问权限为代价):
mysql> REVOKE DELETE ON *.* FROM u1;
mysql> SHOW GRANTS FOR u1;
+-------------------------------------------------+
| Grants for u1@% |
+-------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE ON *.* TO `u1`@`%` |
+-------------------------------------------------+
如果一个帐户在全局和模式级别都有权限,则必须在模式级别撤销两次才能实现部分撤销。假设u1拥有这些权限,其中INSERT在全局和world模式上都有:
mysql> CREATE USER u1;
mysql> GRANT SELECT, INSERT ON *.* TO u1;
mysql> GRANT INSERT ON world.* TO u1;
mysql> SHOW GRANTS FOR u1;
+-----------------------------------------+
| Grants for u1@% |
+-----------------------------------------+
| GRANT SELECT, INSERT ON *.* TO `u1`@`%` |
| GRANT INSERT ON `world`.* TO `u1`@`%` |
+-----------------------------------------+
撤销world上的INSERT会撤销模式级别的特权(SHOW GRANTS不再显示模式级别的GRANT语句):
mysql> REVOKE INSERT ON world.* FROM u1;
mysql> SHOW GRANTS FOR u1;
+-----------------------------------------+
| Grants for u1@% |
+-----------------------------------------+
| GRANT SELECT, INSERT ON *.* TO `u1`@`%` |
+-----------------------------------------+
再次撤销world上的INSERT执行全局特权的部分撤销(SHOW GRANTS现在包括模式级别的REVOKE语句):
mysql> REVOKE INSERT ON world.* FROM u1;
mysql> SHOW GRANTS FOR u1;
+------------------------------------------+
| Grants for u1@% |
+------------------------------------------+
| GRANT SELECT, INSERT ON *.* TO `u1`@`%` |
| REVOKE INSERT ON `world`.* FROM `u1`@`%` |
+------------------------------------------+
部分撤销与明确的模式授予
为某些模式提供帐户访问权限,但不提供其他模式的访问权限,部分撤销提供了一种替代方法,可以在不授予全局特权的情况下明确授予模式级别的访问权限。这两种方法各有优缺点。
授予模式级别特权而不是全局特权:
-
添加新模式:默认情况下,现有帐户无法访问该模式。对于任何应该访问该模式的帐户,DBA 必须授予模式级别的访问权限。
-
添加新帐户:DBA 必须为每个应该访问的模式授予模式级别的访问权限。
在与部分撤销一起授予全局特权:
-
添加新模式:对于具有全局特权的现有帐户,可以访问该模式。对于任何应该无法访问该模式的帐户,DBA 必须添加部分撤销。
-
添加新帐户:DBA 必须授予全局特权,以及对每个受限制的模式进行部分撤销。
使用明确的模式级别授予方法更方便,适用于访问仅限于少数模式的帐户。使用部分撤销的方法更适用于对所有模式具有广泛访问权限的帐户,除了少数模式。
禁用部分撤销
一旦启用,partial_revokes 如果任何帐户具有特权限制,则无法禁用。如果存在这样的帐户,则禁用 partial_revokes 将失败:
-
尝试在启动时禁用
partial_revokes时,服务器会记录错误消息并启用partial_revokes。 -
尝试在运行时禁用
partial_revokes时,会发生错误,并且partial_revokes的值保持不变。
要在存在限制时禁用 partial_revokes,必须首先移除限制:
-
确定哪些帐户具有部分撤销:
SELECT User, Host, User_attributes->>'$.Restrictions' FROM mysql.user WHERE User_attributes->>'$.Restrictions' <> ''; -
对于每个这样的帐户,删除其特权限制。假设前一步显示帐户
u1具有这些限制:[{"Database": "world", "Privileges": ["INSERT", "DELETE"]可以通过多种方式进行限制移除:
-
全局授予权限,无限制:
GRANT INSERT, DELETE ON *.* TO u1; -
在模式级别授予权限:
GRANT INSERT, DELETE ON world.* TO u1; -
全局撤销特权(假设不再需要):
REVOKE INSERT, DELETE ON *.* FROM u1; -
删除帐户本身(假设不再需要):
DROP USER u1;
-
删除所有特权限制后,可以禁用部分撤销:
SET PERSIST partial_revokes = OFF;
部分撤销和复制
在复制场景中,如果任何主机上启用了 partial_revokes,则所有主机上都必须启用。否则,对于在复制发生的所有主机上部分撤销全局特权的 REVOKE 语句可能不会对所有主机产生相同的效果,可能导致复制不一致或错误。
当启用 partial_revokes 时,GRANT 语句的二进制日志中记录了扩展语法,包括发出该语句的当前用户及其当前活动角色。如果以这种方式记录的用户或角色在副本上不存在,则复制应用程序线程会在带有错误的 GRANT 语句处停止。确保在复制源服务器上发出或可能发出 GRANT 语句的所有用户帐户也存在于副本上,并且具有与源服务器上相同的角色集。
8.2.13 权限更改何时生效
如果mysqld服务器在没有--skip-grant-tables选项的情况下启动,它在启动序列期间将所有授权表内容读入内存。在那一点上,内存中的表对访问控制生效。
如果您间接修改授权表,使用帐户管理语句,服务器会立即注意到这些更改并重新加载授权表到内存中。帐户管理语句在第 15.7.1 节“帐户管理语句”中描述。例如包括GRANT、REVOKE、SET PASSWORD和RENAME USER。
如果您直接修改授权表,使用诸如INSERT、UPDATE或DELETE等语句(不建议这样做),则在告知服务器重新加载表或重新启动之前,更改不会影响权限检查。因此,如果您直接更改授权表但忘记重新加载它们,更改将不会生效,直到重新启动服务器。这可能会让您想知道为什么您的更改似乎没有任何影响!
要告知服务器重新加载授权表,请执行刷新权限操作。这可以通过发出FLUSH PRIVILEGES语句或执行mysqladmin flush-privileges或mysqladmin reload命令来完成。
重新加载授权表会影响每个现有客户端会话的权限:
-
表和列权限更改将在客户端的下一个请求中生效。
-
数据库权限更改将在客户端执行
USE *db_name*语句的下一次生效。注意
客户端应用程序可能会缓存数据库名称;因此,这种效果可能对它们不可见,除非实际更改为不同的数据库。
-
静态全局权限和密码对已连接的客户端不受影响。这些更改仅在后续连接的会话中生效。动态全局权限的更改立即生效。有关静态和动态权限之间的区别,请参阅静态与动态权限。
会话中活动角色集的更改立即生效,仅对该会话有效。SET ROLE语句执行会话角色的激活和停用(参见第 15.7.1.11 节,“SET ROLE Statement”)。
如果服务器使用--skip-grant-tables选项启动,则不会读取授权表或实施任何访问控制。任何用户都可以连接并执行任何操作,这是不安全的。要使这样启动的服务器读取表并启用访问检查,请刷新权限。
8.2.14 分配帐户密码
连接到 MySQL 服务器的客户端所需的凭据可能包括密码。本节描述了如何为 MySQL 帐户分配密码。
MySQL 将凭据存储在mysql系统数据库中的user表中。仅允许具有CREATE USER权限的用户执行分配或修改密码的操作,或者具有mysql数据库的权限(INSERT权限用于创建新帐户,UPDATE权限用于修改现有帐户)。如果启用了read_only系统变量,则使用诸如CREATE USER或ALTER USER之类的帐户修改语句还需要CONNECTION_ADMIN权限(或已弃用的SUPER权限)。
这里的讨论仅总结了最常见的密码分配语句的语法。有关其他可能性的完整详细信息,请参见第 15.7.1.3 节,“CREATE USER 语句”,第 15.7.1.1 节,“ALTER USER 语句”和第 15.7.1.10 节,“SET PASSWORD 语句”。
MySQL 使用插件执行客户端身份验证;请参见第 8.2.17 节,“可插拔身份验证”。在分配密码的语句中,与帐户关联的身份验证插件执行指定的明文密码所需的任何哈希处理。这使得 MySQL 能够在将密码存储在mysql.user系统表中之前对其进行混淆。对于这里描述的语句,MySQL 会自动对指定的密码进行哈希处理。还有用于CREATE USER和ALTER USER的语法,允许直接指定哈希值。有关详细信息,请参阅这些语句的描述。
在创建新帐户时分配密码,请使用CREATE USER并包含IDENTIFIED BY子句:
CREATE USER 'jeffrey'@'localhost' IDENTIFIED BY '*password*';
CREATE USER还支持指定帐户身份验证插件的语法。请参见第 15.7.1.3 节,“CREATE USER 语句”。
要为现有帐户分配或更改密码,请使用带有IDENTIFIED BY子句的ALTER USER语句:
ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '*password*';
如果您未连接为匿名用户,可以在不明确命名自己的帐户的情况下更改自己的密码:
ALTER USER USER() IDENTIFIED BY '*password*';
要从命令行更改帐户密码,请使用mysqladmin命令:
mysqladmin -u *user_name* -h *host_name* password "*password*"
此命令设置密码的帐户是mysql.user系统表中具有与User列中的*user_name和Host列中的从中连接的客户端主机*匹配的行的帐户。
警告
使用mysqladmin设置密码应被视为不安全。在某些系统上,您的密码会对系统状态程序(如ps)可见,其他用户可以调用这些程序来显示命令行。MySQL 客户端通常在初始化序列期间用零覆盖命令行密码参数。然而,在这个值可见的瞬间仍然存在。此外,在某些系统上,这种覆盖策略是无效的,密码仍然对ps可见。(SystemV Unix 系统和其他系统可能存在这个问题。)
如果您正在使用 MySQL 复制,请注意,目前,作为CHANGE REPLICATION SOURCE TO语句(从 MySQL 8.0.23 开始)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)的一部分使用的密码实际上被限制为 32 个字符的长度;如果密码更长,任何多余的字符都将被截断。这不是由 MySQL Server 普遍强加的任何限制,而是 MySQL 复制特定的问题。
8.2.15 密码管理
MySQL 支持这些密码管理功能:
-
密码过期,要求定期更改密码。
-
密码重用限制,防止选择旧密码。
-
密码验证,要求密码更改时也需指定要替换的当前密码。
-
双密码,使客户端能够使用主密码或辅助密码连接。
-
密码强度评估,要求使用强密码。
-
随机密码生成,作为要求明确管理员指定文字密码的替代方案。
-
密码失败跟踪,以在连续太多次密码错误登录失败后启用临时帐户锁定。
以下各节描述了这些功能,除了密码强度评估,该功能使用validate_password组件实现,并在第 8.4.3 节,“密码验证组件”中进行了描述。
-
内部与外部凭据存储
-
密码过期策略
-
密码重用策略
-
密码验证要求策略
-
双密码支持
-
随机密码生成
-
登录失败跟踪和临时帐户锁定
重要
MySQL 使用mysql系统数据库中的表来实现密码管理功能。如果您从早期版本升级 MySQL,则您的系统表可能不是最新的。在这种情况下,服务器在启动过程中写入类似以下的错误日志消息(确切的数字可能有所不同):
[ERROR] Column count of mysql.user is wrong. Expected
49, found 47\. The table is probably corrupted
[Warning] ACL table mysql.password_history missing.
Some operations may fail.
要纠正此问题,请执行 MySQL 升级过程。请参阅第三章,升级 MySQL。在此之前,无法更改密码。
内部与外部凭据存储
一些认证插件将帐户凭据存储在 MySQL 内部,存储在mysql.user系统表中:
-
mysql_native_password -
caching_sha2_password -
sha256_password
本节中的大部分讨论适用于这些认证插件,因为这里描述的大多数密码管理功能都是基于 MySQL 本身处理的内部凭据存储。其他认证插件将账户凭据存储在 MySQL 之外。对于使用针对外部凭据系统执行认证的插件的账户,密码管理也必须在该系统外部处理。
例外情况是,对于所有账户,而不仅仅是使用内部凭据存储的账户,失败登录跟踪和临时账户锁定选项都适用,因为 MySQL 能够评估任何账户的登录尝试状态,无论它使用内部还是外部凭据存储。
有关各个认证插件的信息,请参见第 8.4.1 节,“认证插件”。
密码过期策略
MySQL 使数据库管理员能够手动使账户密码过期,并建立自动密码过期策略。过期策略可以在全局范围内建立,并且可以设置个别账户要么遵循全局策略,要么使用特定的每个账户行为覆盖全局策略。
要手动使账户密码过期,请使用ALTER USER语句:
ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE;
此操作会在mysql.user系统表中的相应行中标记密码过期。
根据策略,密码过期是自动的,并且基于密码的年龄,对于给定账户,从其最近更改密码的日期和时间进行评估。mysql.user系统表指示每个账户的密码上次更改的时间,并且如果其年龄大于其允许的寿命,则服务器会在客户端连接时自动将密码视为过期。这可以在没有明确手动密码过期的情况下工作。
要在全局范围内建立自动密码过期策略,请使用default_password_lifetime系统变量。其默认值为 0,这将禁用自动密码过期。如果default_password_lifetime的值是正整数*N,则表示允许的密码寿命,即密码必须每N*天更改一次。
示例:
-
要建立一个全局策略,使密码的寿命约为六个月,请在服务器的
my.cnf文件中添加以下行:[mysqld] default_password_lifetime=180 -
要建立一个全局策略,使密码永不过期,请将
default_password_lifetime设置为 0:[mysqld] default_password_lifetime=0 -
default_password_lifetime也可以在运行时设置和持久化:SET PERSIST default_password_lifetime = 180; SET PERSIST default_password_lifetime = 0;SET PERSIST为正在运行的 MySQL 实例设置一个值。它还保存该值以便在后续服务器重启时继续使用;参见 Section 15.7.6.1, “变量赋值的 SET 语法”。要更改正在运行的 MySQL 实例的值,而不希望其在后续重启时继续使用,使用GLOBAL关键字而不是PERSIST。
全局密码过期策略适用于未设置为覆盖它的所有帐户。要为单个帐户建立策略,请使用 CREATE USER 和 ALTER USER 语句的 PASSWORD EXPIRE 选项。参见 Section 15.7.1.3, “CREATE USER 语句”,以及 Section 15.7.1.1, “ALTER USER 语句”。
示例特定帐户语句:
-
要求每 90 天更改一次密码:
CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY; ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;此过期选项会覆盖语句指定的所有帐户的全局策略。
-
禁用密码过期:
CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE NEVER; ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE NEVER;此过期选项会覆盖语句指定的所有帐户的全局策略。
-
延迟到语句指定的所有帐户的全局过期策略:
CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE DEFAULT; ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE DEFAULT;
当客户端成功连接时,服务器会确定帐户密码是否已过期:
-
服务器会检查密码是否已手动过期。
-
否则,服务器会检查密码的年龄是否超过根据自动密码过期策略允许的寿命。如果是,则服务器会将密码视为过期。
如果密码已过期(无论是手动还是自动),服务器要么断开客户端连接,要么限制其允许的操作(参见 Section 8.2.16, “过期密码的服务器处理”)。受限客户端执行的操作会导致错误,直到用户建立新的帐户密码:
mysql> SELECT 1;
ERROR 1820 (HY000): You must reset your password using ALTER USER
statement before executing this statement.
mysql> ALTER USER USER() IDENTIFIED BY '*password*';
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
客户端重置密码后,服务器将为该会话以及使用该帐户的后续连接恢复正常访问。管理员用户也可以重置帐户密码,但该帐户的任何现有受限会话仍然受限。在执行语句成功之前,使用该帐户的客户端必须断开连接并重新连接。
注意
尽管可以通过将过期密码设置为当前值来“重置”过期密码,但作为良好策略,最好选择不同的密码。DBA 可以通过建立适当的密码重用策略来强制不重用。参见 密码重用策略。
密码重用策略
MySQL 允许对先前密码的重用施加限制。重用限制可以基于密码更改次数、经过的时间或两者来建立。重用策略可以在全局范围内建立,并且可以设置个别帐户要么遵循全局策略,要么用特定的每个帐户行为覆盖全局策略。
帐户的密码历史包括其过去分配的密码。MySQL 可以限制新密码从此历史中选择:
-
如果一个帐户受到密码更改次数的限制,新密码不能选择最近一定数量的最近密码之一。例如,如果最小密码更改次数设置为 3,新密码不能与最近的任何 3 个密码相同。
-
如果一个帐户受到经过时间限制,新密码不能从历史中选择的密码中选择。例如,如果密码重用间隔设置为 60,新密码必须不是在过去 60 天内先前选择的密码之一。
注意
空密码不计入密码历史,并可随时重新使用。
要在全局范围内建立密码重用策略,请使用password_history和password_reuse_interval系统变量。
例子:
-
要禁止重用最后 6 个密码或新于 365 天的密码,请将以下行放入服务器
my.cnf文件中:[mysqld] password_history=6 password_reuse_interval=365 -
要在运行时设置和持久化变量,请使用以下语句:
SET PERSIST password_history = 6; SET PERSIST password_reuse_interval = 365;SET PERSIST为运行中的 MySQL 实例设置一个值。它还保存该值以在后续服务器重新启动时传递;参见 Section 15.7.6.1, “SET Syntax for Variable Assignment”。要更改运行中的 MySQL 实例的值,而不使其在后续重新启动时传递,请使用GLOBAL关键字而不是PERSIST。
全局密码重用策略适用于未被设置为覆盖它的所有帐户。要为个别帐户建立策略,请使用CREATE USER和ALTER USER语句的PASSWORD HISTORY和PASSWORD REUSE INTERVAL选项。参见 Section 15.7.1.3, “CREATE USER Statement”和 Section 15.7.1.1, “ALTER USER Statement”。
个别帐户语句示例:
-
要求至少更改 5 次密码才允许重用:
CREATE USER 'jeffrey'@'localhost' PASSWORD HISTORY 5; ALTER USER 'jeffrey'@'localhost' PASSWORD HISTORY 5;此历史长度选项将覆盖语句命名的所有帐户的全局策略。
-
要求至少 365 天经过才允许重用:
CREATE USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL 365 DAY; ALTER USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL 365 DAY;此经过时间的选项覆盖了语句命名的所有账户的全局策略。
-
要结合两种类型的重用限制,一起使用
PASSWORD HISTORY和PASSWORD REUSE INTERVAL:CREATE USER 'jeffrey'@'localhost' PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 365 DAY; ALTER USER 'jeffrey'@'localhost' PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 365 DAY;这些选项覆盖了语句命名的所有账户的全局策略重用限制。
-
对于两种类型的重用限制,应遵循全局策略:
CREATE USER 'jeffrey'@'localhost' PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT; ALTER USER 'jeffrey'@'localhost' PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT;
需要密码验证的策略
从 MySQL 8.0.13 开始,可以要求验证更改账户密码的尝试,方法是指定要替换的当前密码。这使得 DBA 可以防止用户在未证明知道当前密码的情况下更改密码。否则,例如,如果一个用户暂时离开终端会话而没有注销,那么恶意用户可以使用该会话更改原始用户的 MySQL 密码。这可能会产生不幸的后果:
-
原始用户在管理员重置账户密码之前无法访问 MySQL。
-
直到密码重置发生,恶意用户可以使用良性用户更改的凭据访问 MySQL。
可以在全局范围内建立密码验证策略,并且可以设置单独的账户以延迟全局策略或使用特定的每个账户行为覆盖全局策略。
对于每个账户,其mysql.user行指示是否有一个账户特定设置,要求在尝试更改密码时验证当前密码。该设置由CREATE USER和ALTER USER语句的PASSWORD REQUIRE选项建立:
-
如果账户设置为
PASSWORD REQUIRE CURRENT,密码更改必须指定当前密码。 -
如果账户设置为
PASSWORD REQUIRE CURRENT OPTIONAL,密码更改可能需要但不一定需要指定当前密码。 -
如果账户设置为
PASSWORD REQUIRE CURRENT DEFAULT,password_require_current系统变量确定账户的验证要求策略:-
如果
password_require_current被启用,密码更改必须指定当前密码。 -
如果
password_require_current被禁用,密码更改可能需要但不一定需要指定当前密码。
-
换句话说,如果账户设置不是PASSWORD REQUIRE CURRENT DEFAULT,则账户设置优先于由password_require_current系统变量建立的全局策略。否则,账户将遵循password_require_current设置。
默认情况下,密码验证是可选的:password_require_current已禁用,并且使用无PASSWORD REQUIRE选项创建的帐户默认为PASSWORD REQUIRE CURRENT DEFAULT。
以下表格显示了每个帐户设置如何与password_require_current系统变量值交互,以确定帐户密码验证所需策略。
表 8.10 密码验证策略
| 每个帐户设置 | password_require_current 系统变量 | 密码更改是否需要当前密码? |
|---|---|---|
PASSWORD REQUIRE CURRENT | OFF | 是 |
PASSWORD REQUIRE CURRENT | ON | 是 |
PASSWORD REQUIRE CURRENT OPTIONAL | OFF | 否 |
PASSWORD REQUIRE CURRENT OPTIONAL | ON | 否 |
PASSWORD REQUIRE CURRENT DEFAULT | OFF | 否 |
PASSWORD REQUIRE CURRENT DEFAULT | ON | 是 |
注意
特权用户可以在不指定当前密码的情况下更改任何帐户密码,而不受需要验证的策略限制。特权用户是具有全局CREATE USER权限或mysql系统数据库的UPDATE权限的用户。
要在全局范围内建立密码验证策略,请使用password_require_current系统变量。其默认值为OFF,因此不需要指定当前密码来更改帐户密码。
示例:
-
要建立一个全局策略,要求密码更改必须指定当前密码,请在服务器
my.cnf文件中使用以下行启动服务器:[mysqld] password_require_current=ON -
要在运行时设置并持久化
password_require_current,可以使用以下语句之一:SET PERSIST password_require_current = ON; SET PERSIST password_require_current = OFF;SET PERSIST为正在运行的 MySQL 实例设置一个值。它还保存该值以在后续服务器重新启动时继续使用;请参阅第 15.7.6.1 节,“变量赋值的 SET 语法”。要更改正在运行的 MySQL 实例的值,而不使其在后续重新启动时继续使用,使用GLOBAL关键字而不是PERSIST。
全局密码验证所需策略适用于未被设置为覆盖它的所有帐户。要为单个帐户建立策略,请使用CREATE USER和ALTER USER语句的PASSWORD REQUIRE选项。请参阅第 15.7.1.3 节,“CREATE USER 语句”和第 15.7.1.1 节,“ALTER USER 语句”。
示例特定帐户语句:
-
要求更改密码时指定当前密码:
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT; ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT;此验证选项覆盖了语句命名的所有帐户的全局策略。
-
不要求更改密码时指定当前密码(当前密码可以但不必给出):
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT OPTIONAL; ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT OPTIONAL;此验证选项覆盖了语句命名的所有帐户的全局策略。
-
延迟到语句命名的所有帐户的全局密码验证要求策略:
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT; ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT;
当用户使用ALTER USER或SET PASSWORD语句更改密码时,当前密码的验证起作用。示例使用ALTER USER,这比使用SET PASSWORD更可取,但这里描述的原则对两个语句都是相同的。
在更改密码的语句中,REPLACE子句指定要替换的当前密码。示例:
-
更改当前用户的密码:
ALTER USER USER() IDENTIFIED BY '*auth_string*' REPLACE '*current_auth_string*'; -
更改命名用户的密码:
ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '*auth_string*' REPLACE '*current_auth_string*'; -
更改命名用户的身份验证插件和密码:
ALTER USER 'jeffrey'@'localhost' IDENTIFIED WITH caching_sha2_password BY '*auth_string*' REPLACE '*current_auth_string*';
REPLACE子句的工作原理如下:
-
如果需要为帐户更改密码以指定当前密码,则必须提供
REPLACE,以验证试图进行更改的用户实际上知道当前密码。 -
如果需要为帐户更改密码但不需要指定当前密码,则
REPLACE是可选的。 -
如果指定了
REPLACE,则必须指定正确的当前密码,否则将出现错误。即使REPLACE是可选的也是如此。 -
只有在更改当前用户的帐户密码时才能指定
REPLACE。(这意味着在刚刚显示的示例中,明确命名jeffrey帐户的语句将失败,除非当前用户是jeffrey。)即使特权用户尝试为另一个用户更改密码,也是如此;但是,这样的用户可以更改任何密码而无需指定REPLACE。 -
为避免将明文密码写入二进制日志,
REPLACE被省略。
双密码支持
截至 MySQL 8.0.14,用户帐户被允许具有主密码和辅助密码,称为双密码。双密码功能使得在诸如此类场景中无缝执行凭据更改成为可能:
-
一个系统有大量的 MySQL 服务器,可能涉及复制。
-
多个应用程序连接到不同的 MySQL 服务器。
-
必须定期更改用于连接到服务器的应用程序使用的帐户或帐户的凭据。
考虑在前述类型的场景中执行凭据更改时,当一个账户只允许一个密码时。在这种情况下,必须在账户密码更改和在所有服务器上传播时以及所有使用该账户的应用程序更新以使用新密码的时间上进行紧密合作。这个过程可能涉及停机,期间服务器或应用程序不可用。
通过双重密码,可以更轻松地分阶段进行凭据更改,无需紧密合作,也无需停机:
-
对于每个受影响的账户,在服务器上建立一个新的主密码,将当前密码保留为次要密码。这使得服务器可以识别每个账户的主密码或次要密码,而应用程序可以继续使用与以前相同的密码连接到服务器(现在是次要密码)。
-
在密码更改传播到所有服务器后,修改使用任何受影响账户的应用程序,以使用账户主密码连接。
-
在所有应用程序已从次要密码迁移到主密码后,次要密码不再需要,可以丢弃。此更改传播到所有服务器后,每个账户只能使用主密码连接。凭据更改现在已完成。
MySQL 使用保存和丢弃次要密码的语法实现双重密码功能:
-
RETAIN CURRENT PASSWORD子句用于ALTER USER和SET PASSWORD语句,当您分配新的主密码时,会将账户当前密码保存为其次要密码。 -
DISCARD OLD PASSWORD子句用于ALTER USER,丢弃账户次要密码,仅保留主密码。
假设对于先前描述的凭据更改场景,一个名为'appuser1'@'host1.example.com'的账户被应用程序用于连接服务器,并且要将账户密码从'*password_a*'更改为'*password_b*'。
要执行凭据更改,请使用以下ALTER USER:
-
在每个非副本服务器上,建立
'*password_b*'作为新的appuser1主密码,保留当前密码作为次要密码:ALTER USER 'appuser1'@'host1.example.com' IDENTIFIED BY '*password_b*' RETAIN CURRENT PASSWORD; -
等待密码更改在整个系统中复制到所有副本。
-
修改使用
appuser1账户的每个应用程序,使其使用密码'*password_b*'而不是'*password_a*'连接到服务器。 -
此时,次要密码不再需要。在每个非副本服务器上丢弃次要密码:
ALTER USER 'appuser1'@'host1.example.com' DISCARD OLD PASSWORD; -
在丢弃密码更改已经复制到所有副本之后,凭据更改完成。
RETAIN CURRENT PASSWORD 和 DISCARD OLD PASSWORD 子句具有以下效果:
-
RETAIN CURRENT PASSWORD会保留账户当前密码作为其二级密码,替换任何现有的二级密码。新密码成为主密码,但客户端可以使用主密码或二级密码连接到服务器。 (例外情况:如果由ALTER USER或SET PASSWORD语句指定的新密码为空,则即使给出RETAIN CURRENT PASSWORD,二级密码也会变为空。) -
如果您为具有空主密码的账户指定
RETAIN CURRENT PASSWORD,则该语句将失败。 -
如果一个账户有二级密码,并且您更改其主密码而不指定
RETAIN CURRENT PASSWORD,则二级密码保持不变。 -
对于
ALTER USER,如果更改分配给账户的身份验证插件,则二级密码将被丢弃。 如果更改身份验证插件并且还指定RETAIN CURRENT PASSWORD,则该语句将失败。 -
对于
ALTER USER,DISCARD OLD PASSWORD会丢弃二级密码(如果存在)。账户仅保留其主密码,客户端只能使用主密码连接到服务器。
修改二级密码的语句需要以下权限:
-
要使用
RETAIN CURRENT PASSWORD或DISCARD OLD PASSWORD子句来操作您自己的账户的ALTER USER和SET PASSWORD语句,需要APPLICATION_PASSWORD_ADMIN权限。 大多数用户只需要一个密码,因此需要该权限来操作自己的二级密码。 -
如果要允许一个账户为所有账户操作二级密码,则应授予
CREATE USER权限,而不是APPLICATION_PASSWORD_ADMIN。
随机密码生成
截至 MySQL 8.0.18 版本,CREATE USER,ALTER USER 和 SET PASSWORD 语句具有生成用户账户随机密码的功能,作为明文密码的替代选择。有关语法的详细信息,请参阅每个语句的描述。本节描述了生成随机密码的共同特征。
默认情况下,生成的随机密码长度为 20 个字符。这个长度由generated_random_password_length系统变量控制,范围从 5 到 255。
对于每个语句生成随机密码的账户,该语句将密码存储在mysql.user系统表中,适当地为账户认证插件进行哈希处理。该语句还会在结果集的一行中返回明文密码,以便用户或应用程序执行该语句。结果集列名为user,host,generated password和auth_factor,表示标识mysql.user系统表中受影响行的用户名和主机名值,明文生成的密码,以及显示密码值适用的认证因素。
mysql> CREATE USER
'u1'@'localhost' IDENTIFIED BY RANDOM PASSWORD,
'u2'@'%.example.com' IDENTIFIED BY RANDOM PASSWORD,
'u3'@'%.org' IDENTIFIED BY RANDOM PASSWORD;
+------+---------------+----------------------+-------------+
| user | host | generated password | auth_factor |
+------+---------------+----------------------+-------------+
| u1 | localhost | iOeqf>Mh9:;XD&qn(Hl} | 1 |
| u2 | %.example.com | sXTSAEvw3St-R+_-C3Vb | 1 |
| u3 | %.org | nEVe%Ctw/U/*Md)Exc7& | 1 |
+------+---------------+----------------------+-------------+
mysql> ALTER USER
'u1'@'localhost' IDENTIFIED BY RANDOM PASSWORD,
'u2'@'%.example.com' IDENTIFIED BY RANDOM PASSWORD;
+------+---------------+----------------------+-------------+
| user | host | generated password | auth_factor |
+------+---------------+----------------------+-------------+
| u1 | localhost | Seiei:&cw}8]@3OA64vh | 1 |
| u2 | %.example.com | j@&diTX80l8}(NiHXSae | 1 |
+------+---------------+----------------------+-------------+
mysql> SET PASSWORD FOR 'u3'@'%.org' TO RANDOM;
+------+-------+----------------------+-------------+
| user | host | generated password | auth_factor |
+------+-------+----------------------+-------------+
| u3 | %.org | n&cz2xF;P3!U)+]Vw52H | 1 |
+------+-------+----------------------+-------------+
一个CREATE USER,ALTER USER或SET PASSWORD语句,为一个账户生成一个随机密码,被写入二进制日志作为一个带有IDENTIFIED WITH *auth_plugin* AS '*auth_string*'子句的CREATE USER或ALTER USER语句,其中*auth_plugin*是账户认证插件,'auth_string'是账户哈希密码值。
如果安装了validate_password组件,则其实施的策略对生成的密码没有影响。(密码验证的目的是帮助人类创建更好的密码。)
登录失败跟踪和临时账户锁定
从 MySQL 8.0.19 开始,管理员可以配置用户账户,使得连续登录失败次数过多会导致临时账户锁定。
在这种情况下,“登录失败”意味着客户端在连接尝试期间未提供正确密码。这不包括由于未知用户或网络问题等原因而无法连接的情况。对于具有双重密码的账户(参见双重密码支持),任一账户密码都算正确。
每个账户的所需登录失败次数和锁定时间可通过CREATE USER和ALTER USER语句的FAILED_LOGIN_ATTEMPTS和PASSWORD_LOCK_TIME选项进行配置。示例:
CREATE USER 'u1'@'localhost' IDENTIFIED BY '*password*'
FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 3;
ALTER USER 'u2'@'localhost'
FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME UNBOUNDED;
当连续登录失败次数过多时,客户端会收到如下错误消息:
ERROR 3957 (HY000): Access denied for user *user*.
Account is blocked for *D* day(s) (*R* day(s) remaining)
due to *N* consecutive failed logins.
使用选项如下:
-
FAILED_LOGIN_ATTEMPTS *N*此选项指示是否跟踪指定了不正确密码的账户登录尝试。数字*
N*指定了多少个连续的不正确密码会导致临时账户锁定。 -
PASSWORD_LOCK_TIME {*N* | UNBOUNDED}此选项指示在太多连续登录尝试提供错误密码后锁定帐户的时间。该值是一个数字*
N*,用于指定帐户保持锁定状态的天数,或UNBOUNDED以指定当帐户进入临时锁定状态时,该状态的持续时间是无限的,并且直到帐户被解锁为止。解锁发生的条件将在后面描述。
每个选项的允许值*N*的范围为 0 到 32767。值为 0 会禁用该选项。
失败登录跟踪和临时帐户锁定具有以下特征:
-
要对帐户进行失败登录跟踪和临时锁定,其
FAILED_LOGIN_ATTEMPTS和PASSWORD_LOCK_TIME选项都必须为非零。 -
对于
CREATE USER,如果未指定FAILED_LOGIN_ATTEMPTS或PASSWORD_LOCK_TIME,则其隐式默认值对于语句命名的所有帐户均为 0。这意味着禁用了失败登录跟踪和临时帐户锁定。(这些隐式默认值也适用于在引入失败登录跟踪之前创建的帐户。) -
对于
ALTER USER,如果未指定FAILED_LOGIN_ATTEMPTS或PASSWORD_LOCK_TIME,则其值对于语句命名的所有帐户保持不变。 -
要发生临时帐户锁定,密码失败必须是连续的。在达到失败登录的
FAILED_LOGIN_ATTEMPTS值之前发生的任何成功登录都会导致失败计数重置。例如,如果FAILED_LOGIN_ATTEMPTS为 4,并且已发生三次连续密码失败,则需要再次失败一次才能开始锁定。但是,如果下次登录成功,则帐户的失败登录计数将被重置,因此再次需要四次连续失败才能锁定。 -
一旦临时锁定开始,即使使用正确密码也无法进行成功登录,直到锁定持续时间已过或帐户通过以下讨论中列出的帐户重置方法之一被解锁。
当服务器读取授权表时,它会初始化每个帐户的状态信息,包括是否启用了失败登录跟踪,帐户当前是否被临时锁定以及如果是的话锁定何时开始,以及如果帐户未被锁定,则在临时锁定发生之前的失败次数。
帐户的状态信息可以被重置,这意味着失败登录计数被重置,并且如果当前被临时锁定,则帐户被解锁。帐户重置可以是全局的,适用于所有帐户,也可以是每个帐户:
-
所有帐户的全局重置发生在以下任何条件下:
-
服务器重新启动。
-
执行
FLUSH PRIVILEGES。(使用--skip-grant-tables选项启动服务器会导致授予权限表不被读取,从而禁用了失败登录跟踪。在这种情况下,第一次执行FLUSH PRIVILEGES会导致服务器读取授予权限表并启用失败登录跟踪,同时重置所有账户。)
-
-
对于任何以下条件,每个账户都会发生重置:
-
账户成功登录。
-
锁定持续时间过去。在这种情况下,失败登录计数将在下次登录尝试时重置。
-
对于账户执行设置
FAILED_LOGIN_ATTEMPTS或PASSWORD_LOCK_TIME(或两者)任何值(包括当前选项值)的ALTER USER语句的执行,或对于账户执行ALTER USER ... UNLOCK语句。对于账户的其他
ALTER USER语句对其当前的失败登录计数或锁定状态没有影响。
-
失败登录跟踪与用于检查凭据的登录账户相关联。如果使用用户代理,跟踪将针对代理用户而不是被代理用户进行。也就是说,跟踪与USER()指示的账户相关联,而不是与CURRENT_USER()指示的账户相关联。有关代理用户和被代理用户之间区别的信息,请参阅第 8.2.19 节,“代理用户”。
8.2.16 服务器处理过期密码
原文:
dev.mysql.com/doc/refman/8.0/en/expired-password-handling.html
MySQL 提供了密码过期功能,使数据库管理员可以要求用户重置他们的密码。密码可以手动过期,也可以根据自动过期策略(参见第 8.2.15 节,“密码管理”)。
ALTER USER语句可以启用账户密码过期。例如:
ALTER USER 'myuser'@'localhost' PASSWORD EXPIRE;
对于使用过期密码的账户的每个连接,服务器要么断开客户端连接,要么将客户端限制在“沙盒模式”中,其中服务器只允许客户端执行重置过期密码所需的操作。服务器采取的操作取决于客户端和服务器设置,稍后将讨论。
如果服务器断开客户端连接,它会返回一个ER_MUST_CHANGE_PASSWORD_LOGIN错误:
$> mysql -u myuser -p
Password: ******
ERROR 1862 (HY000): Your password has expired. To log in you must
change it using a client that supports expired passwords.
如果服务器将客户端限制在沙盒模式中,客户端会话内允许执行以下操作:
-
客户端可以使用
ALTER USER或SET PASSWORD重置账户密码。完成后,服务器将为该会话以及使用该账户的后续连接恢复正常访问。注意
尽管可以通过将过期密码重置为当前值来“重置”过期密码,但作为良好策略,最好选择一个不同的密码。数据库管理员可以通过建立适当的密码重用策略来强制执行不重复使用。参见密码重用策略。
-
在 MySQL 8.0.27 之前,客户端可以使用
SET语句。从 MySQL 8.0.27 开始,不再允许此操作。
对于会话中不允许的任何操作,服务器会返回一个ER_MUST_CHANGE_PASSWORD错误:
mysql> USE performance_schema;
ERROR 1820 (HY000): You must reset your password using ALTER USER
statement before executing this statement.
mysql> SELECT 1;
ERROR 1820 (HY000): You must reset your password using ALTER USER
statement before executing this statement.
这通常发生在交互式调用mysql客户端时,因为默认情况下这种调用会被放置在沙盒模式中。要恢复正常功能,请选择一个新密码。
对于mysql客户端的非交互式调用(例如,在批处理模式下),如果密码过期,服务器通常会断开客户端的连接。为了允许非交互式的mysql调用保持连接,以便可以更改密码(使用沙盒模式中允许的语句),请在mysql命令中添加--connect-expired-password选项。
如前所述,服务器是否断开过期密码客户端的连接或将其限制在沙盒模式取决于客户端和服务器设置的组合。以下讨论描述了相关设置及其交互方式。
注意
此讨论仅适用于密码过期的帐户。如果客户端使用未过期的密码连接,则服务器会正常处理客户端。
在客户端端,给定的客户端指示它是否可以处理过期密码的沙盒模式。对于使用 C 客户端库的客户端,有两种方法可以做到这一点:
-
在连接之前向
mysql_options()传递MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS标志:bool arg = 1; mysql_options(mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, &arg);这是在mysql客户端中使用的技术,如果以交互方式调用或使用
--connect-expired-password选项,则启用MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS。 -
在连接时向
mysql_real_connect()传递CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS标志:MYSQL mysql; mysql_init(&mysql); if (!mysql_real_connect(&mysql, host, user, password, db, port, unix_socket, CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS)) { ... handle error ... }
其他 MySQL 连接器有其自己的约定,用于指示准备处理沙盒模式。请参阅您感兴趣的连接器的文档。
在服务器端,如果客户端指示它可以处理过期密码,服务器会将其置于沙盒模式。
如果客户端没有指示它可以处理过期密码(或者使用无法指示的较旧版本的客户端库),则服务器的操作取决于disconnect_on_expired_password系统变量的值:
-
如果
disconnect_on_expired_password已启用(默认情况下),服务器将以ER_MUST_CHANGE_PASSWORD_LOGIN错误断开客户端的连接。 -
如果
disconnect_on_expired_password已禁用,则服务器将客户端置于沙盒模式。
8.2.17 可插拔认证
原文:
dev.mysql.com/doc/refman/8.0/en/pluggable-authentication.html
当客户端连接到 MySQL 服务器时,服务器使用客户端提供的用户名和客户端主机来从mysql.user系统表中选择适当的账户行。然后服务器对客户端进行认证,从账户行确定适用于客户端的认证插件:
-
如果服务器找不到插件,则会发生错误并拒绝连接尝试。
-
否则,服务器会调用该插件来对用户进行认证,插件会向服务器返回一个状态,指示用户是否提供了正确的密码并被允许连接。
可插拔认证使得以下重要功能成为可能:
-
认证方法的选择。 可插拔的认证使得数据库管理员可以轻松选择和更改用于各个 MySQL 账户的认证方法。
-
外部认证。 可插拔认证使得客户端可以使用适合于在
mysql.user系统表之外存储凭据的认证方法进行连接到 MySQL 服务器。例如,可以创建插件来使用外部认证方法,如 PAM、Windows 登录 ID、LDAP 或 Kerberos。 -
代理用户: 如果用户被允许连接,认证插件可以向服务器返回一个与连接用户名称不同的用户名,以指示连接用户是另一个用户(被代理用户)的代理。在连接持续期间,代理用户在访问控制方面被视为具有被代理用户的权限。实际上,一个用户冒充另一个用户。更多信息,请参见第 8.2.19 节,“代理用户”。
注意
如果使用--skip-grant-tables选项启动服务器,则即使加载了认证插件,也不会使用认证插件,因为服务器不执行客户端认证并允许任何客户端连接。由于这是不安全的,如果使用--skip-grant-tables选项启动服务器,则还会通过启用skip_networking来禁用远程连接。
-
可用认证插件
-
默认认证插件
-
认证插件使用
-
认证插件客户端/服务器兼容性
-
认证插件连接器编写注意事项
-
可插拔认证的限制
可用的认证插件
MySQL 8.0 提供了这些认证插件:
-
一个执行本地认证的插件;即,在 MySQL 引入可插拔认证之前使用的基于密码哈希方法的认证。
mysql_native_password插件实现了基于此本地密码哈希方法的认证。参见 Section 8.4.1.1,“本地可插拔认证”。注意
截至 MySQL 8.0.34,
mysql_native_password认证插件已被弃用,并可能在将来的 MySQL 版本中被移除。 -
使用 SHA-256 密码哈希进行认证的插件。这比本地认证提供的加密更强大。参见 Section 8.4.1.2,“缓存 SHA-2 可插拔认证”,以及 Section 8.4.1.3,“SHA-256 可插拔认证”。
-
一个客户端插件,将密码以明文形式发送到服务器,不经过哈希或加密。此插件与需要以客户端用户提供的密码完全一致的服务器端插件一起使用。参见 Section 8.4.1.4,“客户端明文插件认证”。
-
一个使用 PAM(可插拔认证模块)执行外部认证的插件,使 MySQL 服务器能够使用 PAM 对 MySQL 用户进行认证。此插件还支持代理用户。参见 Section 8.4.1.5,“PAM 可插拔认证”。
-
一个在 Windows 上执行外部认证的插件,使 MySQL 服务器能够使用本地 Windows 服务对客户端连接进行认证。已登录到 Windows 的用户可以根据其环境中的信息从 MySQL 客户端程序连接到服务器,而无需指定额外的密码。此插件还支持代理用户。参见 Section 8.4.1.6,“Windows 可插拔认证”。
-
使用 LDAP(轻量级目录访问协议)进行认证的插件,通过访问诸如 X.500 之类的目录服务来认证 MySQL 用户。这些插件还支持代理用户。参见第 8.4.1.7 节,“LDAP 可插拔认证”。
-
一个使用 Kerberos 进行认证的插件,用于认证与 Kerberos 主体对应的 MySQL 用户。参见第 8.4.1.8 节,“Kerberos 可插拔认证”。
-
一个阻止所有使用它的帐户的客户端连接的插件。此插件的用例包括永远不允许直接登录但仅通过代理帐户访问的代理帐户和必须能够以提升的权限执行存储程序和视图而不向普通用户公开这些权限的帐户。参见第 8.4.1.9 节,“无登录可插拔认证”。
-
一个插件,用于验证通过 Unix 套接字文件从本地主机连接的客户端。参见第 8.4.1.10 节,“套接字对等凭证可插拔认证”。
-
一个通过 FIDO 认证将用户认证到 MySQL 服务器的插件。参见第 8.4.1.11 节,“FIDO 可插拔认证”。
注意
截至 MySQL 8.0.35 版本,
authentication_fido和authentication_fido_client认证插件已被弃用,并可能在将来的 MySQL 版本中被移除。 -
一个测试插件,检查帐户凭据并将成功或失败记录到服务器错误日志中。此插件旨在用于测试和开发目的,并作为编写认证插件的示例。参见第 8.4.1.12 节,“测试可插拔认证”。
注意
有关当前对可插拔认证使用的限制的信息,包括哪些连接器支持哪些插件,请参阅可插拔认证限制。
第三方连接器开发人员应阅读该部分,以确定连接器可以利用可插拔认证功能的程度,以及成为更加符合规范的步骤。
如果您有兴趣编写自己的认证插件,请参阅编写认证插件。
默认认证插件
CREATE USER和ALTER USER语句有用于指定账户如何进行认证的语法。这种语法的某些形式没有明确指定认证插件(没有IDENTIFIED WITH子句)。例如:
CREATE USER 'jeffrey'@'localhost' IDENTIFIED BY '*password*';
在这种情况下,服务器会为账户分配默认的认证插件。在 MySQL 8.0.27 之前,这个默认值是default_authentication_plugin系统变量的值。
截至 MySQL 8.0.27,引入了多因素认证,可以有最多三个子句指定账户如何进行认证。确定没有指定插件的认证方法的默认认证插件的规则是特定于因素的:
-
因素 1:如果
authentication_policy元素 1 指定了一个认证插件,那个插件就是默认的。如果authentication_policy元素 1 是*,那么default_authentication_plugin的值就是默认的。根据上述规则,以下语句创建了一个双因素认证账户,第一因素认证方法由
authentication_policy或default_authentication_plugin设置确定:CREATE USER 'wei'@'localhost' IDENTIFIED BY '*password*' AND IDENTIFIED WITH authentication_ldap_simple;同样地,这个示例创建了一个三因素认证账户:
CREATE USER 'mateo'@'localhost' IDENTIFIED BY '*password*' AND IDENTIFIED WITH authentication_ldap_simple AND IDENTIFIED WITH authentication_fido;你可以使用
SHOW CREATE USER来查看应用的认证方法。 -
因素 2 或 3:如果相应的
authentication_policy元素指定了一个认证插件,那个插件就是默认的。如果authentication_policy元素是*或空的,那么就没有默认值;尝试为没有指定插件的因素定义账户认证方法是一个错误,就像以下示例一样:mysql> CREATE USER 'sofia'@'localhost' IDENTIFIED WITH authentication_ldap_simple AND IDENTIFIED BY 'abc'; ERROR 1524 (HY000): Plugin '' is not loaded mysql> CREATE USER 'sofia'@'localhost' IDENTIFIED WITH authentication_ldap_simple AND IDENTIFIED BY 'abc'; ERROR 1524 (HY000): Plugin '*' is not loaded
认证插件使用
本节提供了安装和使用认证插件的一般说明。有关特定插件的说明,请参阅描述该插件的部分,位于 Section 8.4.1, “Authentication Plugins”下。
一般来说,可插拔认证在服务器和客户端两侧使用一对相应的插件,因此你可以像这样使用给定的认证方法:
-
如果需要,安装包含适当插件的插件库或插件库。在服务器主机上,安装包含服务器端插件的库,以便服务器可以用它来验证客户端连接。同样,在每个客户端主机上,安装包含客户端插件的库,供客户端程序使用。内置的认证插件无需安装。
-
对于每个创建的 MySQL 帐户,指定要用于验证的适当服务器端插件。如果帐户要使用默认的认证插件,则帐户创建语句无需明确指定插件。服务器分配默认的认证插件,如默认认证插件中所述确定的那样。
-
当客户端连接时,服务器端插件告诉客户端程序要使用哪个客户端插件进行验证。
如果一个帐户使用的认证方法对于服务器和客户端程序都是默认的,那么服务器无需通知客户端要使用哪个客户端插件,可以避免客户端/服务器协商中的往返。
对于标准的 MySQL 客户端,如mysql和mysqladmin,可以在命令行上指定--default-auth=*plugin_name*选项,作为程序可以期望使用的客户端插件的提示,尽管如果与用户帐户关联的服务器端插件需要不同的客户端插件,则服务器会覆盖此设置。
如果客户端程序找不到客户端插件库文件,请指定--plugin-dir=*dir_name*选项以指示插件库目录位置。
认证插件客户端/服务器兼容性
可插拔认证为 MySQL 帐户的认证方法选择提供了灵活性,但在某些情况下,由于客户端和服务器之间的认证插件不兼容,可能无法建立客户端连接。
对于成功连接到给定服务器上给定帐户的客户端的一般兼容性原则是,客户端和服务器都必须支持帐户所需的认证方法。因为认证方法由认证插件实现,所以客户端和服务器都必须支持帐户所需的认证插件。
认证插件不兼容可能以各种方式出现。例如:
-
使用 5.7.22 或更低版本的 MySQL 5.7 客户端连接到一个使用
caching_sha2_password进行认证的 MySQL 8.0 服务器账户。这会失败,因为 5.7 客户端不识别这个插件,这个插件是在 MySQL 8.0 中引入的。(这个问题在 MySQL 5.7 中已经得到解决,从 5.7.23 开始,MySQL 客户端库和客户端程序添加了对caching_sha2_password的客户端支持。) -
使用 MySQL 5.7 客户端连接到一个使用
mysql_old_password进行认证的低于 5.7 版本的服务器账户。这会因为多种原因而失败。首先,这样的连接需要--secure-auth=0,这不再是一个支持的选项。即使它被支持,5.7 客户端也不会识别这个插件,因为它在 MySQL 5.7 中被移除了。 -
使用来自 Community 发行版的 MySQL 5.7 客户端连接到一个使用企业专用 LDAP 认证插件之一进行认证的 MySQL 5.7 企业服务器账户。这会失败,因为 Community 客户端无法访问企业插件。
一般来说,当客户端和服务器来自同一 MySQL 发行版时,这些兼容性问题不会出现。当客户端和服务器来自不同的 MySQL 系列时,问题可能会出现。当 MySQL 引入新的认证插件或移除旧插件时,这些问题是开发过程中固有的。为了最大程度地减少不兼容性的可能性,定期及时升级服务器、客户端和连接器。
认证插件连接器编写注意事项
存在多种 MySQL 客户端/服务器协议的实现。libmysqlclient C API 客户端库是其中一种实现。一些 MySQL 连接器(通常不是用 C 语言编写的)提供了它们自己的实现。然而,并非所有协议实现都以相同的方式处理插件认证。本节描述了协议实现者应该考虑的一个认证问题。
在客户端/服务器协议中,服务器会告诉连接的客户端它认为是默认的认证插件。如果客户端使用的协议实现尝试加载默认插件,而该插件在客户端上不存在,加载操作将失败。如果默认插件不是客户端尝试连接的账户实际需要的插件,这是一个不必要的失败。
如果客户端/服务器协议实现没有自己的默认认证插件概念,并始终尝试加载服务器指定的默认插件,如果该插件不可用,则会出现错误。
为了避免这个问题,客户端使用的协议实现应该有自己的默认插件,并将其作为首选(或者在无法加载服务器指定的默认插件时,回退到这个默认插件)。例如:
-
在 MySQL 5.7 中,
libmysqlclient默认选择要么是mysql_native_password,要么是通过MYSQL_DEFAULT_AUTH选项为mysql_options()指定的插件。 -
当 5.7 客户端尝试连接到 8.0 服务器时,服务器将
caching_sha2_password指定为其默认认证插件,但客户端仍然根据mysql_native_password或通过MYSQL_DEFAULT_AUTH指定的凭据详细信息发送。 -
客户端仅在更改插件请求时加载服务器指定的插件,但在这种情况下,它可以是任何取决于用户帐户的插件。在这种情况下,客户端必须尝试加载插件,如果该插件不可用,则错误是不可选的。
可插拔认证限制
本节的第一部分描述了在第 8.2.17 节,“可插拔认证”中描述的可插拔认证框架的适用性的一般限制。第二部分描述了第三方连接器开发人员如何确定连接器可以利用可插拔认证功能的程度以及采取哪些步骤以变得更加符合规范。
此处使用的“本机认证”术语指的是针对存储在mysql.user系统表中的密码进行的认证。这是在实施可插拔认证之前由较旧的 MySQL 服务器提供的相同认证方法。“Windows 本机认证”是指使用已经登录到 Windows 的用户的凭据进行认证,由 Windows 本机认证插件(简称“Windows 插件”)实施。
-
通用可插拔认证限制
-
可插拔认证和第三方连接器
通用可插拔认证限制
-
Connector/C++: 使用此连接器的客户端只能通过使用本机认证的帐户连接到服务器。
例外:如果连接器是动态链接到
libmysqlclient(而不是静态链接)构建的,并且如果安装了该版本,则加载当前版本的libmysqlclient,或者如果连接器从源代码重新编译以链接到当前的libmysqlclient。有关编写连接器以处理有关默认服务器端认证插件信息的信息,请参阅认证插件连接器编写注意事项。
-
Connector/NET: 使用 Connector/NET 的客户端可以通过使用本地认证或 Windows 本地认证的帐户连接到服务器。
-
Connector/PHP: 使用此连接器的客户端只能通过使用 MySQL 本机驱动程序(
mysqlnd)编译时使用本地认证的帐户连接到服务器。 -
Windows 本地认证: 通过使用 Windows 插件的帐户连接需要 Windows 域设置。如果没有设置,将使用 NTLM 认证,然后只有本地连接是可能的;也就是说,客户端和服务器必须在同一台计算机上运行。
-
代理用户: 代理用户支持程度取决于客户端是否可以通过使用实现代理用户功能的插件进行身份验证的帐户连接(即,可以返回与连接用户不同的用户名的插件)。例如,PAM 和 Windows 插件支持代理用户。
mysql_native_password和sha256_password认证插件默认不支持代理用户,但可以进行配置;请参阅服务器支持代理用户映射。 -
复制: 复制副本不仅可以使用本地认证的复制用户帐户,还可以通过使用非本地认证的复制用户帐户连接,如果所需的客户端端插件可用。如果插件内置于
libmysqlclient中,则默认可用。否则,插件必须安装在副本端的由副本的plugin_dir系统变量命名的目录中。 -
FEDERATED表:FEDERATED表只能通过在远程服务器上使用本地认证的帐户访问远程表。
可插拔认证和第三方连接器
第三方连接器开发人员可以使用以下准则来确定连接器是否准备好利用可插拔认证功能,并采取哪些步骤以提高兼容性:
-
对于未进行任何更改的现有连接器,使用本地认证,使用该连接器的客户端只能通过使用本地认证的帐户连接到服务器。但是,您应该针对服务器的最新版本测试连接器,以验证这样的连接是否仍然可以正常工作。
异常情况:如果连接器动态链接到
libmysqlclient(而不是静态链接),并且加载当前安装的libmysqlclient版本,则连接器可能可以在不进行任何更改的情况下与可插拔认证一起使用。 -
要利用可插拔认证功能,基于
libmysqlclient的连接器应该重新链接到当前版本的libmysqlclient。这使连接器能够支持通过现在内置到libmysqlclient中的客户端插件的账户进行连接(例如用于 PAM 认证所需的明文插件和用于 Windows 本地认证所需的 Windows 插件)。与当前的libmysqlclient链接还使连接器能够访问安装在默认 MySQL 插件目录中的客户端插件(通常是由本地服务器的plugin_dir系统变量的默认值命名的目录)。如果连接器动态链接到
libmysqlclient,必须确保在客户端主机上安装了更新版本的libmysqlclient,并且连接器在运行时加载它。 -
连接器支持给定认证方法的另一种方式是直接在客户端/服务器协议中实现它。Connector/NET 使用这种方法来提供对 Windows 本地认证的支持。
-
如果连接器应该能够从与默认插件目录不同的目录加载客户端插件,那么它必须实现一些方式让客户用户指定目录。这方面的可能性包括通过命令行选项或环境变量,从中连接器可以获取目录名称。标准的 MySQL 客户端程序,如mysql和mysqladmin实现了一个
--plugin-dir选项。另请参阅 C API 客户端插件接口。 -
连接器对代理用户的支持取决于,正如本节前面所述的,它支持的认证方法是否允许代理用户。
8.2.18 多因素认证
原文:
dev.mysql.com/doc/refman/8.0/en/multifactor-authentication.html
认证涉及一方向第二方证明其身份。多因素认证(MFA)是在认证过程中使用多个认证值(或“因素”)的方法。MFA 比单因素认证(1FA)提供更高的安全性,单因素认证仅使用一种认证方法,比如密码。MFA 可以启用额外的认证方法,比如使用多个密码进行认证,或者使用智能卡、安全密钥和生物识别读卡器进行认证。
MySQL 8.0.27 及更高版本支持多因素认证。该功能包括需要最多三个认证值的 MFA 形式。也就是说,MySQL 账户管理支持使用 2FA 或 3FA 的账户,除了现有的 1FA 支持。
当客户端尝试使用单因素账户连接到 MySQL 服务器时,服务器调用账户定义中指定的认证插件,并根据插件报告的成功或失败来接受或拒绝连接。
对于具有多个认证因素的账户,流程类似。服务器按照账户定义中列出的顺序调用认证插件。如果插件报告成功,服务器会接受连接(如果插件是最后一个),或者如果还有插件剩余,则继续调用下一个插件。如果任何插件报告失败,服务器将拒绝连接。
以下章节详细介绍了 MySQL 中的多因素认证。
-
多因素认证支持的元素
-
配置多因素认证策略
-
开始使用多因素认证
多因素认证支持的元素
认证因素通常包括以下类型的信息:
-
你知道的东西,比如秘密密码或口令。
-
你拥有的东西,比如安全密钥或智能卡。
-
你的生物特征;比如指纹或面部扫描。
“你所知道的”因素类型依赖于在身份验证过程的双方都保密的信息。不幸的是,秘密可能会受到威胁:有人可能看到你输入密码,或者用网络钓鱼攻击欺骗你,服务器端存储的密码可能会在安全漏洞中暴露等。使用多个密码可以提高安全性,但每个密码仍然可能会受到威胁。使用其他因素类型可以在减少威胁的情况下提高安全性。
MySQL 中多因素身份验证的实现包括以下元素:
-
authentication_policy系统变量控制可以使用多少身份验证因素以及每个因素允许的身份验证类型。也就是说,它对于CREATE USER和ALTER USER语句在多因素身份验证方面施加了约束。 -
CREATE USER和ALTER USER具有语法,允许为新帐户指定多个身份验证方法,并为现有帐户添加、修改或删除身份验证方法。如果帐户使用 2FA 或 3FA,mysql.user系统表会在User_attributes列中存储有关额外身份验证因素的信息。 -
为了启用使用需要多个密码的帐户进行 MySQL 服务器身份验证,客户端程序具有
--password1、--password2和--password3选项,允许指定最多三个密码。对于使用 C API 的应用程序,mysql_options4()C API 函数的MYSQL_OPT_USER_PASSWORD选项可以实现相同的功能。 -
服务器端的
authentication_fido插件(已弃用)允许使用设备进行身份验证。这个服务器端的 FIDO 身份验证插件仅包含在 MySQL Enterprise Edition 发行版中。它不包含在 MySQL 社区发行版中。然而,客户端的authentication_fido_client插件(已弃用)包含在所有发行版中,包括社区发行版。这使得来自任何发行版的客户端可以连接到使用authentication_fido在加载了该插件的服务器上进行身份验证的帐户。参见 Section 8.4.1.11, “FIDO Pluggable Authentication”。 -
authentication_fido还可以实现免密码认证,如果它是账户唯一使用的认证插件。查看 FIDO 免密码认证。 -
多因素认证可以使用非 FIDO MySQL 认证方法、FIDO 认证方法或两者的组合。
-
这些权限使用户能够执行某些受限的多因素认证相关操作:
-
拥有
AUTHENTICATION_POLICY_ADMIN权限的用户不受authentication_policy系统变量强加的限制。 (对于否则不允许的语句会发出警告。) -
PASSWORDLESS_USER_ADMIN权限允许创建免密码认证账户并对其进行操作复制。
-
配置多因素认证策略
authentication_policy 系统变量定义了多因素认证策略。 具体来说,它定义了账户可以拥有(或需要拥有)多少认证因素以及可以用于每个因素的认证方法。
authentication_policy 的值是一个由 1、2 或 3 个逗号分隔的元素组成的列表。 列表中的每个元素对应一个认证因素,可以是认证插件名称、星号(*)、空或缺失。 (例外:元素 1 不能是空或缺失。) 整个列表用单引号括起来。 例如,以下authentication_policy 值包括一个星号、一个认证插件名称和一个空元素:
authentication_policy = '*,authentication_fido,'
星号(*)表示需要认证方法,但允许任何方法。 空元素表示认证方法是可选的,且允许任何方法。 缺失的元素(没有星号、空元素或认证插件名称)表示不允许认证方法。 当指定插件名称时,创建或修改账户时需要该认证方法作为相应因素的必需方法。
默认authentication_policy 值为'*,,'(一个星号和两个空元素),需要第一因素,并可选择允许第二和第三因素。 因此,默认authentication_policy 值与现有的 1FA 账户向后兼容,但也允许创建或修改账户以使用 2FA 或 3FA。
拥有AUTHENTICATION_POLICY_ADMIN权限的用户不受authentication_policy设置所施加的约束。 (对于否则不允许的语句会发出警告。)
authentication_policy值可以在选项文件中定义或使用SET GLOBAL语句指定:
SET GLOBAL authentication_policy='*,*,';
有几条规则规定了如何定义authentication_policy值。请参考authentication_policy系统变量描述,了解这些规则的完整说明。以下表格提供了几个authentication_policy示例值及其所建立的策略。
表 8.11 示例 authentication_policy 值
| authentication_policy 值 | 有效策略 |
|---|---|
'*' | 仅允许创建或更改具有一个因素的帐户。 |
'*,*' | 仅允许创建或更改具有两个因素的帐户。 |
'*,*,*' | 仅允许创建或更改具有三个因素的帐户。 |
'*,' | 允许创建或更改具有一或两个因素的帐户。 |
'*,,' | 允许创建或更改具有一、两或三个因素的帐户。 |
'*,*,' | 允许创建或更改具有两个或三个因素的帐户。 |
'*,*auth_plugin*' | 允许创建或更改具有两个因素的帐户,其中第一个因素可以是任何认证方法,第二个因素必须是指定的插件。 |
'*auth_plugin*,*,' | 允许创建或更改具有两个或三个因素的帐户,其中第一个因素必须是指定的插件。 |
'*auth_plugin*,' | 允许创建或更改具有一或两个因素的帐户,其中第一个因素必须是指定的插件。 |
'*auth_plugin*,*auth_plugin*,*auth_plugin*' | 允许创建或更改具有三个因素的帐户,其中因素必须使用指定的插件。 |
| authentication_policy 值 | 有效策略 |
开始使用多因素认证
默认情况下,MySQL 使用一个多因素认证策略,允许任何认证插件作为第一个因素,并可选择允许第二和第三个认证因素。此策略是可配置的;有关详细信息,请参阅配置多因素认证策略。
注意
不允许使用任何内部凭据存储插件(caching_sha2_password或mysql_native_password)作为第 2 或第 3 因素。
假设您希望一个账户首先使用 caching_sha2_password 插件进行认证,然后再使用 authentication_ldap_sasl SASL LDAP 插件进行认证。(这假设 LDAP 认证已经按照 Section 8.4.1.7, “LDAP Pluggable Authentication” 中描述的设置完成,并且用户在 LDAP 目录中有一个与示例中显示的认证字符串对应的条目。)使用以下语句创建账户:
CREATE USER 'alice'@'localhost'
IDENTIFIED WITH caching_sha2_password
BY '*sha2_password*'
AND IDENTIFIED WITH authentication_ldap_sasl
AS 'uid=u1_ldap,ou=People,dc=example,dc=com';
要连接,用户必须提供两个密码。为了启用使用需要多个密码的账户连接到 MySQL 服务器的认证,客户端程序具有 --password1、--password2 和 --password3 选项,允许指定最多三个密码。这些选项类似于 --password 选项,可以在命令行上跟随选项后面输入密码值(这是不安全的),或者如果没有密码值,则会提示用户输入密码。对于刚创建的账户,因素 1 和 2 需要密码,因此使用 --password1 和 --password2 选项调用 mysql 客户端。mysql 依次提示输入每个密码:
$> mysql --user=alice --password1 --password2
Enter password: *(enter factor 1 password)* Enter password: *(enter factor 2 password)*
假设您想要添加第三个认证因素。可以通过删除并重新创建具有第三个因素的用户,或者使用 ALTER USER *user* ADD *factor* 语法来实现。以下两种方法都显示如下:
DROP USER 'alice'@'localhost';
CREATE USER 'alice'@'localhost'
IDENTIFIED WITH caching_sha2_password
BY '*sha2_password*'
AND IDENTIFIED WITH authentication_ldap_sasl
AS 'uid=u1_ldap,ou=People,dc=example,dc=com'
AND IDENTIFIED WITH authentication_fido;
ADD *factor* 语法包括因素编号和 FACTOR 关键字:
ALTER USER 'alice'@'localhost' ADD 3 FACTOR IDENTIFIED WITH authentication_fido;
ALTER USER *user* DROP *factor* 语法允许删除一个因素。以下示例删除了前一个示例中添加的第三个因素(authentication_fido):
ALTER USER 'alice'@'localhost' DROP 3 FACTOR;
ALTER USER *user* MODIFY *factor* 语法允许更改特定因素的插件或认证字符串,前提是该因素存在。以下示例修改了第二个因素,将认证方法从 authentication_ldap_sasl 更改为 authetication_fido:
ALTER USER 'alice'@'localhost' MODIFY 2 FACTOR IDENTIFIED WITH authentication_fido;
使用 SHOW CREATE USER 查看为账户定义的认证方法:
SHOW CREATE USER 'u1'@'localhost'\G
*************************** 1\. row ***************************
CREATE USER for u1@localhost: CREATE USER `u1`@`localhost`
IDENTIFIED WITH 'caching_sha2_password' AS '*sha2_password*'
AND IDENTIFIED WITH 'authentication_fido' REQUIRE NONE
PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY
DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE
CURRENT DEFAULT
8.2.19 代理用户
MySQL 服务器使用认证插件对客户端连接进行身份验证。认证给定连接的插件可能要求将连接的(外部)用户视为不同用户以进行特权检查。这使得外部用户可以成为第二用户的代理;也就是说,承担第二用户的权限:
-
外部用户是“代理用户”(可以冒充或成为另一个用户的用户)。
-
第二个用户是“被代理用户”(其身份和特权可以被代理用户所假定)。
本节描述了代理用户功能的工作原理。有关认证插件的一般信息,请参阅第 8.2.17 节“可插拔认证”。有关特定插件的信息,请参阅第 8.4.1 节“认证插件”。有关编写支持代理用户的认证插件的信息,请参阅在认证插件中实现代理用户支持。
-
代理用户支持要求
-
简单代理用户示例
-
防止直接登录代理帐户
-
授予和撤销 PROXY 特权
-
默认代理用户
-
默认代理用户和匿名用户冲突
-
服务器支持代理用户映射
-
代理用户系统变量
注意
代理的一个管理优势是,DBA 可以设置一个具有一组特权的单个帐户,然后启用多个代理用户具有这些特权,而无需为每个用户单独分配特权。作为代理用户的替代方案,DBA 可能会发现角色提供了一种适合将用户映射到特定命名特权集的方法。每个用户可以被授予一个给定的单一角色,以实际上被授予适当的特权集。请参阅第 8.2.10 节“使用角色”。
代理用户支持要求
要对给定认证插件进行代理,必须满足以下条件:
-
必须支持代理,可以是插件本身支持,也可以是 MySQL 服务器代表插件支持。在后一种情况下,可能需要显式启用服务器支持;参见服务器支持代理用户映射。
-
外部代理用户的账户必须设置为由插件进行身份验证。使用
CREATE USER语句将账户与身份验证插件关联,或使用ALTER USER更改其插件。 -
被代理用户的账户必须存在并被授予代理用户所需的权限。使用
CREATE USER和GRANT语句进行此操作。 -
通常,代理用户被配置为仅在代理场景中使用,而不用于直接登录。
-
代理用户账户必须具有
PROXY权限以供被代理账户使用。使用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的代理认证是否对employee_ext用户可行。如果未授予此权限,则会发生错误。否则,employee_ext假定employee的权限。服务器通过检查由employee_ext在客户端会话期间执行的语句来针对授予employee的权限。在这种情况下,employee_ext可以访问employees数据库中的表。
代理账户employee使用mysql_no_login认证插件来防止客户端直接使用该账户登录。(假设插件已安装。有关说明,请参见 Section 8.4.1.9, “无登录可插入式认证”。)要了解保护代理账户免受直接使用的替代方法,请参见防止直接登录到代理账户。
当进行代理时,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认证插件关联。在这种情况下,账户无论如何都不能用于直接登录。这假定插件已安装。有关说明,请参见 Section 8.4.1.9, “No-Login Pluggable Authentication”。 -
创建账户时包括
ACCOUNT LOCK选项。参见 Section 15.7.1.3, “CREATE USER Statement”。使用这种方法时,还要包括一个密码,这样如果账户稍后解锁,就不能在没有密码的情况下访问它。(如果启用了validate_password组件,则不允许创建没有密码的账户,即使账户被锁定。参见 Section 8.4.3, “The Password Validation Component”。) -
创建带有密码的账户,但不要告诉其他人密码。如果不让任何人知道账户的密码,客户端就无法使用它直接连接到 MySQL 服务器。
授予和撤销 PROXY 权限
需要 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 权限:
-
由具有
proxied_user的GRANT PROXY ... WITH GRANT OPTION权限的用户。 -
由
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;
这些语句创建一个可以管理所有 GRANT PROXY 映射的 admin 用��。例如,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帐户进行身份验证,仍然会使用这些帐户,因此应该保护这些代理帐户免受直接登录的影响(请参阅防止直接登录到代理帐户)。
默认代理用户和匿名用户冲突
如果您打算创建一个默认代理用户,请检查其他现有的“匹配任何用户”帐户,因为它们可能优先于默认代理用户,从而阻止该用户按预期工作。
在前面的讨论中,默认代理用户账户在主机部分有'',匹配任何主机。如果设置了默认代理用户,请务必检查是否存在具有相同用户部分和'%'主机部分的非代理账户,因为'%'也匹配任何主机,但根据服务器用于内部排序账户行的规则,'%'优先于''(请参阅第 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的匿名用户连接。 -
使用具名的默认账户而不是匿名的默认账户。有关此技术的示例,请参考使用
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 对 proxied_user 有 PROXY 权限,会话将继续进行,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。