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_name
User
和Host
值来自user
表中的行。假设jeffrey
连接并执行以下查询:
mysql> SELECT CURRENT_USER();
+----------------+
| CURRENT_USER() |
+----------------+
| @localhost |
+----------------+
这里显示的结果表明,匹配的user
表行中有一个空白的User
列值。换言之,服务器将jeffrey
视为匿名用户。
诊断认证问题另一种方法是打印出user
表,并手动排序以查看第一个匹配点在哪里被做出的。