8.2.6 访问控制,第一阶段:连接验证
当您尝试连接到 MySQL 服务器时,服务器根据以下条件接受或拒绝连接:
-
您的身份是否可以通过提供正确的凭证来验证。
-
您的账户是否锁定或解锁。
服务器首先检查凭证,然后检查账户锁定状态。任何一步骤失败,服务器都会拒绝您完全访问。否则,服务器接受连接,然后进入第二阶段,等待请求。
服务器使用 user 表中的列进行身份和凭证检查,只有满足这些条件时才接受连接:
-
客户端主机名和用户名与
user表某一行的Host和User列匹配。关于可允许的Host和User值的规则,请参见第 8.2.4 节,“指定账户名”。 -
客户端提供了该行中指定的凭证(例如,密码),如
authentication_string列所示。凭证使用在plugin列中命名的身份验证插件进行解释。 -
该行表明账户已解锁。锁定状态记录在
account_locked列中,该列的值必须为'N'。账户锁定可以使用CREATE USER或ALTER USER语句进行设置或修改。
您的身份基于两个信息:
-
您的MySQL用户名。
-
您连接的客户机主机。
如果User列值非空, incoming 连接的用户名必须完全匹配。如果User值为空,它将匹配任何用户名。如果 incoming 连接的user表行具有空用户名,则该用户被视为无名用户,而不是客户端实际指定的用户名。这意味着在连接期间(即 Stage 2),将使用空用户名进行所有进一步访问检查。
authentication_string列可以为空。这不是通配符,也不表示任何密码匹配。它表示用户必须无密码连接。该插件实现的身份验证方法可能会或不会在authentication_string列中使用密码。在这种情况下,外部密码也可能用于对 MySQL 服务器进行身份验证。
用户表中的非空密码值存储在 authentication_string 列中,MySQL 不将密码以明文形式存储,而是使用账户认证插件实现的加密方法对密码进行加密,然后在连接过程中检查密码是否正确。这个过程不需要加密后的密码传输。详见第8.2.1节,“帐户用户名和密码”。
从 MySQL 服务器的角度看,加密后的密码是真正的密码,所以你不应该将其给任何人访问。特别是,不要将非管理员用户授予 mysql 系统数据库表的读取权限。
以下表格展示了 User 和 Host 值在用户表中的各种组合如何应用于 incoming 连接。
User Value |
Host Value |
Permissible Connections |
|---|---|---|
'fred' |
'h1.example.net' |
fred, 从 h1.example.net 连接 |
'' |
'h1.example.net' |
任何用户,来自 h1.example.net |
'fred' |
'%' |
fred, 从任意主机连接 |
'' |
'%' |
任何用户,来自任意主机 |
'fred' |
'%.example.net' |
fred,来自example.net域中的任何主机 |
'fred' |
'x.example.%' |
fred,来自x.example.net、x.example.com、x.example.edu等;这可能不是很有用 |
'fred' |
'198.51.100.177' |
fred,来自IP地址为198.51.100.177的主机 |
'fred' |
'198.51.100.%' |
fred,来自198.51.100类C子网中的任何主机 |
'fred' |
'198.51.100.0/255.255.255.0' |
与前一个示例相同 |
incoming连接的客户端主机名和用户名可能在user表中匹配多行。前面的示例演示了这个问题:一些条目显示了来自h1.example.net的连接是由fred建立的。
当有多个匹配结果时,服务器必须确定使用哪一个。它解决这个问题如下:
-
服务器读取
user表时,它将行排序。 -
当客户端尝试连接时,服务器遍历已排序的行。
-
服务器使用第一个匹配客户端主机名和用户名的行。
服务器按照最具特征的Host值进行排序:
-
字面IP地址和主机名是最具特征的。
-
具有主机部分为IP地址的账户顺序如下:
-
主机部分给定为IP地址的账户:
CREATE USER 'user_name'@'127.0.0.1'; CREATE USER 'user_name'@'198.51.100.44'; -
主机部分使用CIDR notation给定的账户:
CREATE USER 'user_name'@'192.0.2.21/8'; CREATE USER 'user_name'@'198.51.100.44/16'; -
主机部分给定为IP地址并带有子网掩码的账户:
CREATE USER 'user_name'@'192.0.2.0/255.255.255.0'; CREATE USER 'user_name'@'198.51.0.0/255.255.0.0';
-
-
模式
'%'表示“任何主机”,最不具体。 -
空字符串
''也表示“任何主机”,但在'%'之后排序。
非TCP(socket文件、命名管道和共享内存)连接被视为本地连接,如果存在相应账户,或者否则匹配localhost主机部分(例如,local%,l%,%)。
将'%'视为等同于localhost的处理方式已弃用;您应该预期未来MySQL版本中删除该行为。
具有相同Host值的行按照最不具体的User值排序。空白User值表示“任何用户”,最不具体,所以具有相同Host值的非匿名用户在匿名用户之前。
具有等价Host和User值的行顺序不确定。
要了解这个工作方式,假设user表格如下:
+-----------+----------+-
| Host | User | ...
+-----------+----------+-
| % | root | ...
| % | jeffrey | ...
| localhost | root | ...
| localhost | | ...
+-----------+----------+-
服务器读取表数据时,使用前面描述的规则对行进行排序。排序结果如下:
+-----------+----------+-
| Host | User | ...
+-----------+----------+-
| localhost | root | ...
| localhost | | ...
| % | jeffrey | ...
| % | root | ...
+-----------+----------+-
当客户端尝试连接时,服务器遍历已排序的行,并使用找到的第一个匹配项。对于来自localhost的jeffrey连接,表中有两个行匹配:一行的Host和User值为'localhost'和'',另一行的值为'%'和'jeffrey'。排序结果中'localhost'行排在前,所以服务器使用该行。
这是另一个示例。假设user表如下:
+----------------+----------+-
| Host | User | ...
+----------------+----------+-
| % | jeffrey | ...
| h1.example.net | | ...
+----------------+----------+-
已排序的表如下:
+----------------+----------+-
| Host | User | ...
+----------------+----------+-
| h1.example.net | | ...
| % | jeffrey | ...
+----------------+----------+-
第一个行匹配来自h1.example.net的任何用户,而第二个行匹配来自jeffrey的任何主机。
人们常常误认为,服务器在尝试找到连接时,对于给定的用户名,所有明确命名该用户名的行都被使用。实际上,这不是真的。前面的示例就说明了这个问题,来自h1.example.net的jeffrey连接首先匹配的是无用户名的行,而不是包含'jeffrey'的User列值。因此,jeffrey被认证为匿名用户,即使他在连接时指定了用户名。
如果您可以连接到服务器,但权限不如预期,您可能被认证为其他账户。要找到服务器用来认证您的账户,使用CURRENT_USER()函数。(参见第14.15节,“信息函数”。)它返回一个以格式的值,表示匹配的user_name@host_nameUser和Host值来自user表中的行。假设jeffrey连接并执行以下查询:
mysql> SELECT CURRENT_USER();
+----------------+
| CURRENT_USER() |
+----------------+
| @localhost |
+----------------+
这里显示的结果表明,匹配的user表行中有一个空白的User列值。换言之,服务器将jeffrey视为匿名用户。
诊断认证问题另一种方法是打印出user表,并手动排序以查看第一个匹配点在哪里被做出的。