8.2.3 授权表
`mysql` 系统数据库包含多个授权表,用于存储用户账户和他们拥有的权限信息。这一节描述这些表。关于系统数据库中其他表的信息,请见第7.3节,“The mysql System Schema”。
本节讨论授权表的 underlying 结构和服务器在与客户端交互时如何使用它们的内容。然而,通常您不直接修改授权表。修改是通过使用帐户管理语句,如CREATE USER
、GRANT
和REVOKE
来设置账户和控制每个账户可用的权限的。见第15.7.1节,“Account Management Statements”。当您使用这些语句执行帐户操作时,服务器将在您的 behalf 修改授权表。
不建议使用语句,如INSERT
、UPDATE
或DELETE
直接修改授权表。服务器可以忽略由于这些修改而变得不正确的行。
对任何修改授权表的操作,服务器都会检查该表是否具有预期的结构,如果不是,则产生错误。要将表更新到预期的结构,请执行 MySQL 升级程序。见第 3 章, 升级 MySQL。
这些mysql
数据库表包含授权信息:
-
user
: 用户账户、静态全局权限和其他非权限列。 -
global_grants
: 动态全局权限。 -
db
: 数据库级别权限。 -
tables_priv
: 表级别权限。 -
columns_priv
: 列级权限。 -
procs_priv
: 存储过程和函数权限。 -
proxies_priv
: 代理用户权限。 -
default_roles
: 默认用户角色。 -
role_edges
: 角色子图的边缘。 -
password_ history
: 密码更改历史记录。
关于静态和动态全局权限的差异,见静态 versus 动态权限。)
MySQL 8.4中,授权表使用InnoDB
存储引擎,并且是事务性的。之前的 MySQL 8.4,授权表使用MyISAM
存储引擎,并且是非事务性的。这次更改授权表存储引擎的变化使得账户管理语句,如CREATE USER
或GRANT
,的行为也发生了变化。之前,如果账户管理语句中指定多个用户,可以成功对一些用户失败对其他用户。现在,每个语句都是事务性的,如果出现任何错误,它将回滚并没有任何影响。
每个授权表包含范围列和权限列:
-
作用域列确定每个表行的作用域,即该行适用的上下文。例如,一个
user
表行具有Host
和User
值为'h1.example.net'
和'bob'
,适用于服务器从主机h1.example.net
接收来自客户端的认证连接,该客户端指定用户名为bob
。类似地,一个db
表行具有Host
、User
和Db
列值为'h1.example.net'
、'bob'
和'reports'
,适用于bob
从主机h1.example.net
连接到访问reports
数据库的存储库。表tables_priv
和columns_priv
包含作用域列,指示每个行适用的表或表/列组合。表procs_priv
中的作用域列指示每个行适用的存储程序。 -
权限列指示一个表行授予的权限,即它允许执行哪些操作。服务器将各种授权表中的信息组合成完整的用户权限描述。第8.2.7节,“访问控制,第二阶段:请求验证”中描述了这些规则。
此外,一张授权表可能包含用于其他目的的列,而不是作用域或权限评估。
服务器使用授权表的方式如下:
-
用户表的
user
范围列确定是否拒绝或允许 incoming 连接。对于允许的连接,用户表中的任何权限都表示用户的静态全局权限。用户表中的任何权限都适用于服务器上的所有数据库。Caution由于任何静态全局权限都是对所有数据库的权限,因此任何静态全局权限都使得用户可以使用
SHOW DATABASES
语句或通过检查INFORMATION_SCHEMA
的SCHEMATA
表来查看所有数据库名称,except those that have been restricted at the database level by partial revokes。 -
用户账户的当前动态全局权限分配列表在
global_ grants
表中。对于每一行,范围列确定拥有该权限的用户是谁,该权限列中的权限名称。 -
数据库表的范围列确定哪些用户可以从哪些主机访问哪些数据库。权限列确定允许的操作。数据库级别授予的权限适用于数据库和数据库中的所有对象,如表和存储程序。
-
tables_priv
和columns_priv
表类似于数据库表,但更细粒度:它们适用于表和列级别,而不是数据库级别。授予在表级别的权限适用于该表和该表中的所有列。授予在列级别的权限只适用于特定列。 -
存储过程(存储程序和函数)相关的
procs_priv
表。对某个存储程序或函数授予的权限仅适用于单个程序或函数。 -
proxies_priv
表指示哪些用户可以作为其他用户的代理,并且一个用户是否可以将PROXY
权限授予给其他用户。 -
default_roles
和role_edges
表包含角色关系的信息。 -
password_history
表保留了之前选择的密码,以便限制密码重复使用。请参阅第8.2.15节,“密码管理”。
服务器在启动时将grant表的内容读入内存中。你可以通过执行FLUSH PRIVILEGES
语句或执行mysqladmin flush-privileges或mysqladmin reload命令来重新加载grant表。grant表的更改将在第8.2.13节,“权限更改生效时”中所示生效。
当您修改账户时,建议验证更改的效果是否符合预期。要检查给定账户的权限,请使用SHOW GRANTS
语句。例如,要确定用户名和主机名值为bob
和pc84.example.com
的账户授予的权限,请使用以下语句:
SHOW GRANTS FOR 'bob'@'pc84.example.com';
要显示账户的非权限属性,请使用SHOW CREATE USER
:
SHOW CREATE USER 'bob'@'pc84.example.com';
用户和db 授权表
服务器在访问控制的第一和第二阶段中使用mysql
数据库中的user
和db
表(见第8.2节,“访问控制和账户管理”)。在user
和db
表中有以下列。
Table 8.4 user and db Table Columns
Table Name | user |
db |
---|---|---|
Scope columns | Host |
Host |
User |
Db |
|
User |
||
Privilege columns | Select_priv |
Select_priv |
Insert_priv |
Insert_priv |
|
Update_priv |
Update_priv |
|
Delete_priv |
Delete_priv |
|
Index_priv |
Index_priv |
|
Alter_priv |
Alter_priv |
|
Create_priv |
Create_priv |
|
Drop_priv |
Drop_priv |
|
Grant_priv |
Grant_priv |
|
Create_view_priv |
Create_view_priv |
|
Show_view_priv |
Show_view_priv |
|
Create_routine_priv |
Create_routine_priv |
|
Alter_routine_priv |
Alter_routine_priv |
|
Execute_priv |
Execute_priv |
|
Trigger_priv |
Trigger_priv |
|
Event_priv |
Event_priv |
|
Create_tmp_table_priv |
Create_tmp_table_priv |
|
Lock_tables_priv |
Lock_tables_priv |
|
References_priv |
References_priv |
|
Reload_priv |
||
Shutdown_priv |
||
Process_priv |
||
File_priv |
||
Show_db_priv |
||
Super_priv |
||
Repl_slave_priv |
||
Repl_client_priv |
||
Create_user_priv |
||
Create_tablespace_priv |
||
Create_role_priv |
||
Drop_role_priv |
||
Security columns | ssl_type |
|
ssl_cipher |
||
x509_issuer |
||
x509_subject |
||
plugin |
||
authentication_string |
||
password_expired |
||
password_last_changed |
||
password_lifetime |
||
account_locked |
||
Password_reuse_history |
||
Password_reuse_time |
||
Password_require_current |
||
User_attributes |
||
Resource control columns | max_questions |
|
max_updates |
||
max_connections |
||
max_user_connections |
用户表中的plugin
和authentication_string
列存储身份验证插件和凭证信息。
服务器使用用户账户行的plugin
列中指定的插件来.authenticate连接尝试。
plugin
列不能为空。启动时和在执行FLUSH PRIVILEGES
语句时,服务器检查用户表行。如果存在空的plugin
列,服务器将写入错误日志中的警告,以以下形式:
[Warning] User entry 'user_name'@'host_name' has an empty plugin
value. The user will be ignored and no one can login with this user
anymore.
要为缺少插件的账户分配插件,请使用ALTER USER
语句。
password_expired
列允许DBA过期账户密码,并要求用户重置密码。默认的password_expired
值是'N'
ALTER USER
语句将其设置为'Y'
。在账户密码过期后,用户在服务器的所有操作都将导致错误,直到用户使用ALTER USER
语句来建立新的账户密码。
虽然可以通过将密码设置为当前值来““重置””过期的密码,但作为良好的政策,建议选择不同的密码。DBA可以通过建立适当的密码重用策略来强制不重复使用密码。请参阅Password Reuse Policy。
password_last_changed
是一个TIMESTAMP
列,表示密码最后更改的时间。该值仅在使用 MySQL 内置身份验证插件(mysql_native_password
、sha256_password
或caching_sha2_password
)的账户中非空。对于其他账户,例如使用外部身份验证系统的账户,该值为NULL
。
password_last_changed
由CREATE USER
、ALTER USER
、SET PASSWORD
语句和GRANT
语句更新,用于创建账户或更改账户密码。
password_lifetime
表示账户密码的生命周期,单位为天。如果密码已经超过其生命周期(使用password_last_changed
列进行评估),服务器将在客户端连接时认为密码已过期。值为N
大于零表示密码必须每N
天更改一次。值为0则禁用自动密码过期。如果值为NULL
(默认),则应用全局过期策略,根据default_ password_lifetime
系统变量定义。
account_locked
表示账户是否锁定(见第8.2.20节,“Account Locking”)。
Password_ reuse_history
是账户的PASSWORD HISTORY
选项值,或者为默认历史记录NULL
。
Password_ reuse_time
是账户的PASSWORD REUSE INTERVAL
选项值,或者为默认间隔NULL
。
Password_require_current
对应账户的PASSWORD REQUIRE
选项值,如下表所示。
表8.5 允许的Password_require_current值
Password_require_current Value | Corresponding PASSWORD REQUIRE Option |
---|---|
'Y' |
PASSWORD REQUIRE CURRENT |
'N' |
PASSWORD REQUIRE CURRENT OPTIONAL |
NULL |
密码要求当前默认 |
User_attributes
是一个 JSON 格式的列,用于存储账户属性,而不是存储在其他列中。INFORMATION_SCHEMA
通过USER_ATTRIBUTES
表来暴露这些属性。
User_attributes
列可能包含以下属性:
-
additional_password
: 如果存在,secondary 密码。请参阅Dual Password Support。 -
Restrictions
: 如果存在,限制列表。限制是通过部分撤销操作添加的。该属性值是一个数组,每个元素都有Database
和Restrictions
键,表示受限数据库的名称和对其的适用限制(请参阅Section 8.2.12, “Privilege Restriction Using Partial Revokes”)。 -
密码锁定
: 失败登录跟踪和临时账户锁定的条件,如果有(见失败登录跟踪和临时账户锁定)。密码锁定
属性根据CREATE USER
和ALTER USER
语句的FAILED_LOGIN_ATTEMPTS
和PASSWORD_LOCK_TIME
选项进行更新。该属性值是一个哈希,其中包含failed_login_attempts
和password_lock_time_days
键,表示为该账户指定的选项的值。如果一个键缺失,它的值被隐式设置为0。如果一个键值被隐式或显式设置为0,该对应的能力将被禁用。 -
多因素身份验证
:mysql.user
系统表中的行有一个plugin
列,表示身份验证插件。对于单因素身份验证,该插件是唯一的身份验证因素。对于两因素或三因素多因素身份验证,该插件对应于第一个身份验证因素,但需要存储第二和第三个因素的信息。多因素身份验证
属性持有这些信息。该
多因素身份验证
值是一个数组,每个数组元素是哈希,描述身份验证因素使用以下属性:-
插件
: 认证插件的名称。 -
认证字符串
: 认证字符串值。 -
无密码
: 表示用户是否需要使用安全令牌作为唯一身份验证方法的标志。 -
需要注册
: 定义用户账户是否已注册安全令牌的标志。
数组的前两个元素描述了多因素身份验证因子2和3。
-
如果无属性适用,User_attributes
为NULL
。
示例:拥有第二个密码和部分撤销数据库权限的账户在列值中具有additional_password
和Restrictions
属性:
mysql> SELECT User_attributes FROM mysql.User WHERE User = 'u'\G
*************************** 1. row ***************************
User_attributes: {"Restrictions":
[{"Database": "mysql", "Privileges": ["SELECT"]}],
"additional_password": "hashed_credentials"}
要确定哪些属性存在,请使用JSON_KEYS()
函数:
SELECT User, Host, JSON_KEYS(User_attributes)
FROM mysql.user WHERE User_attributes IS NOT NULL;
要提取特定的属性,例如Restrictions
SELECT User, Host, User_attributes->>'$.Restrictions'
FROM mysql.user WHERE User_attributes->>'$.Restrictions' <> '';
下面是一个关于
multi_factor_authentication
的示例信息存储:
{
"multi_factor_authentication": [
{
"plugin": "authentication_ldap_simple",
"passwordless": 0,
"authentication_string": "ldap auth string",
"requires_registration": 0
},
{
"plugin": "authentication_webauthn",
"passwordless": 0,
"authentication_string": "",
"requires_registration": 1
}
]
}
授权表tables_priv和columns_priv
在第二阶段的访问控制中,服务器执行请求验证,以确保每个客户端对其发出的每个请求都具有足够的权限。除了user
和db
授权表外,服务器还可能会 consulted tables_priv
和 columns_priv
表,以处理涉及表的请求。后者表提供了更细粒度的权限控制,用于表和列级别。它们具有以下表格所示的列。
表8.6:tables_priv 和 columns_priv 表列
Table Name | tables_priv |
columns_priv |
---|---|---|
Scope columns | Host |
Host |
Db |
Db |
|
User |
User |
|
Table_name |
Table_name |
|
Column_name |
||
Privilege columns | Table_priv |
Column_priv |
Column_priv |
||
Other columns | Timestamp |
Timestamp |
Grantor |
Timestamp
和 Grantor
列将被设置为当前时间戳和CURRENT_USER
值,但否则未使用。
验证涉及存储程序的请求时,服务器可能会咨询procs_priv
表,该表具有以下表格所示的列。
Table 8.7 procs_priv Table Columns
Table Name | procs_priv |
---|---|
Scope columns | Host |
Db |
|
User |
|
Routine_name |
|
Routine_type |
|
Privilege columns | Proc_priv |
Other columns | Timestamp |
Grantor |
Routine_type
列是一个 ENUM
列,其值为 'FUNCTION'
或 'PROCEDURE'
,用于指示该行所指例程的类型。此列允许分别为具有相同名称的函数和过程授予权限。
Timestamp
和 Grantor
列未使用。
proxies_priv
表格记录了代理帐户的信息。它包含以下列:
为了使一个账户能够授予PROXY
特权给其他账户,它必须在proxies_priv
表中有一个行,With_授权
设置为1,并且Proxied_主机
和Proxied_用户
设置为指示可以授予特权的账户或账户列表。例如,MySQL安装时创建的'root'@'localhost'
账户在proxies_priv
表中有一个行,使得授予PROXY
特权给''@''
,即所有用户和所有主机。这使得root
可以设置代理账户,同时也可以将授权权委托给其他账户,以便它们可以设置代理账户。请参阅第8.2.19节,“Proxy Users”。
The global_授权
表列出了当前分配给用户帐户的动态全局权限。这个表有这些列:
-
用户
,主机
: 被授予特权的帐户的用户名和主机名。 -
PRIV
: 权限名称。 -
WITH_GRANT_OPTION
: 账户是否可以将权限授予其他账户。
默认用户角色表default_roles
列出默认用户角色。它具有以下列:
-
HOST
,USER
: 权限或角色的账户或角色。 -
DEFAULT_ROLE_HOST
,DEFAULT_ROLE_USER
: 默认角色。
角色的边缘表role_edges
列出角色的子图。它具有以下列:
-
FROM_HOST
,FROM_USER
: 授予角色的账户。 -
TO_HOST
,TO_USER
: 授予给账户的角色。 -
WITH_ADMIN_OPTION
: 账户是否可以使用WITH ADMIN OPTION
授予角色的权限和撤销权限。
密码历史表password_history
包含关于密码更改的信息。它具有以下列:
-
Host
,User
: 密码更改发生的账户。 -
Password_timestamp
: 密码更改发生的时间戳。 -
Password
: 新密码哈希值。
密码历史表password_history
累积足够数量的非空密码,以便MySQL能够对账户密码历史长度和重用间隔进行检查。自动删除超出这两个限制的条目发生在密码更改尝试时。
空密码不计入密码历史中,并且可以随时重用。
如果账户被重命名,它们的条目将被重命名以匹配。如果账户被删除或其身份验证插件更改,它们的条目将被删除。
授权表中的范围列包含字符串。每个列的默认值都是空字符串。以下表格显示了每个列允许的字符数。
表8.8 授权表范围列长度
Column Name | Maximum Permitted Characters |
---|---|
Host , Proxied_host |
255 |
User , Proxied_user |
32 |
Db |
64 |
Table_name |
64 |
Column_name |
64 |
Routine_name |
64 |
Host
和 Proxied_host
值将在存储到授权表时转换为小写。
为了访问检查目的,User
、Proxied_user
、authentication_string
、Db
和Table_name
值的比较是区分大小写的。对Host
、Proxied_host
、Column_name
和Routine_name
值的比较不是区分大小写的。
user
和db
表中,每个特权在一个单独的列中被声明为ENUM('N','Y') DEFAULT 'N'
。换言之,每个特权都可以禁用或启用,默认情况下是禁用的。
tables_priv
、columns_priv
和procs_priv
表中,特权列被声明为SET
列。这些列中的值可以包含由该表控制的所有特权的任意组合。只有列值中列出的特权是启用的。
Table 8.9 Set-Type Privilege Column Values
Table Name | Column Name | Possible Set Elements |
---|---|---|
tables_priv |
Table_priv |
'Select', 'Insert', 'Update', 'Delete', 'Create', 'Drop', 'Grant', 'References', 'Index', 'Alter', 'Create View', 'Show view', 'Trigger' |
tables_priv |
Column_priv |
'Select', 'Insert', 'Update', 'References' |
columns_priv |
Column_priv |
'Select', 'Insert', 'Update', 'References' |
procs_priv |
Proc_priv |
'Execute', 'Alter Routine', 'Grant' |
只有user
和global_ grants
表指定了管理权限,如RELOAD
、SHUTDOWN
和SYSTEM_VARIABLES_ADMIN
。管理操作是对服务器本身的操作,而不是数据库特定的操作,因此没有理由将这些权限列在其他授权表中。因此,服务器只需要consultuser
和global_ grants
表来确定用户是否可以执行管理操作。
FILE
权限也只在user
表中指定。它不是管理权限,而是用户对服务器主机的文件读取或写入能力,这与访问的数据库无关。
为了允许MySQL授权表上的并发DML和DDL操作,之前在MySQL授权表上获取行锁的读操作将被执行为非锁定读操作。对MySQL授权表进行非锁定读操作的操作包括:
-
SELECT
语句和其他只读语句,通过连接列表和子查询从授权表中读取数据,包括SELECT ... FOR SHARE
语句,无论使用哪种事务隔离级别。 -
读取授权表数据(通过连接列表或子查询)但不修改它们的 DML 操作,无论使用哪种事务隔离级别。
从授权表中读取数据的语句,如果在使用 statement-基于复制时执行,将报告警告,不会获取行锁。
使用 binlog_ format=mixed
时,读取授权表数据的 DML 操作将被写入二进制日志中,以使操作安全地支持混合模式复制。
SELECT ... FOR SHARE
语句从授权表中读取数据将报告警告。使用 FOR SHARE
take 语句时,不支持在授权表上获取读锁。
使用 SERIALIZABLE
隔离级别执行的 DML 操作,读取授权表数据将报告警告。通常情况下,在使用 SERIALIZABLE
隔离级别时获取的读锁在授权表上不受支持。