权限系统工作原理
MySQL权限系统保证所有的用户可以严格地做他们假定被允许做的事情。当你连接一个MySQL服务器时, 你的身份由你从那连接的主机和你指定的用户名来决定,系统根据你的身份和你想做什么来授予权限。
MySQL在认定身份中考虑你的主机名和用户名字,是因为有很小的原因假定一个给定的用户在因特网上属于同一个人。例如,用户从whitehouse.gov连接的bill不必和从mosoft.com连接bill是同一个人。 MySQL通过允许你区分在不同的主机上碰巧有同样名字用户来处理它:你可以对从whitehouse.gov连接授与bill一个权限集,而为从microsoft.com的连接授予一个不同的权限集。
MySQL存取控制包含2个阶段:
阶段1:服务器检查你是否允许连接。
阶段2:假定你能连接,服务器检查你发出的每个请求。看你是否有足够的权限实施它。例如,如果你从数据库中一个表精选(select)行或从数据库抛弃一个表,服务器确定你对表有select权限或对数据库有drop权限。
服务器在存取控制的两个阶段使用在mysql的数据库中的user、db和host表,在这些授权表中字段如下:
表名称 user db host
范围字段 Host Host Host
User Db Db
Password User
权限字段 Select_priv Select_priv Select_priv
Insert_priv Insert_priv Insert_priv
Update_priv Update_priv Update_priv
Delete_priv Delete_priv Delete_priv
Index_priv Index_priv Index_priv
Alter_priv Alter_priv Alter_priv
Create_priv Create_priv Create_priv
Drop_priv Drop_priv Drop_priv
Grant_priv Grant_priv Grant_priv
Reload_priv
Shutdown_priv
Process_priv
File_priv
对存取控制的第二阶段(请求证实),如果请求涉及表,服务器可以另外参考tables_priv和columns_priv表。这些表的字段如下:
表名称 tables_priv columns_priv
范围字段 Host Host
Db Db
User User
Table_name Table_name
Column_name
权限字段 Table_priv Column_priv
Column_priv
其他字段 Timestamp Timestamp
Grantor
每个授权表包含范围字段和权限字段。
范围字段决定表中每个条目的范围,即,条目适用的上下文。例如, 一个user表条目的Host和User值为'thomas.loc.gov'和'bob'将被用于证实来自主机thomas.loc.gov的bob对服务器的连接。同样,一个db表条目的Host、User和Db字段的值是'thomas.loc.gov'、'bob'和'reports'将用在bob从主机联接thomas.loc.gov存取reports数据库的时候。 tables_priv和columns_priv表包含范围字段,指出每个条目适用的表或表/列的组合。
对于检查存取的用途,比较Host值是忽略大小写的。User、Password、Db和Table_name值是区分大小写的。Column_name值在MySQL3.22.12或以后版本是忽略大小写的。
权限字段指出由一个表条目授予的权限,即,可实施什么操作。服务器组合各种的授权表的信息形成一个用户权限的完整描述。为此使用的规则在6.8 存取控制, 阶段2:请求证实描述。
范围字段是字符串,如下所述;每个字段的缺省值是空字符串:
字段名 类型
Host CHAR(60)
User CHAR(16)
Password CHAR(16)
Db CHAR(64) (tables_priv和columns_priv表为CHAR(60))
在user、db和host表中,所有权限字段被声明为ENUM('N','Y')--每一个都可有值'N'或'Y',并且缺省值是'N'.
在tables_priv和columns_priv表中,权限字段被声明为SET字段:
表名 字段名 可能的集合成员
tables_priv Table_priv 'Select', 'Insert', 'Update', 'Delete', 'Create', 'Drop', 'Grant', 'References', 'Index', 'Alter'
tables_priv Column_priv 'Select', 'Insert', 'Update', 'References'
columns_priv Column_priv 'Select', 'Insert', 'Update', 'References'
简单地说,服务器使用这样的授权表:
user表范围字段决定是否允许或拒绝到来的连接。对于允许的连接,权限字段指出用户的全局(超级用户)权限。
db和host表一起使用:
db表范围字段决定用户能从哪个主机存取哪个数据库。权限字段决定允许哪个操作。
当你想要一个给定的db条目应用于若干主机时,host表作为db表的扩展被使用。例如,如果你想要一个用户能在你的网络从若干主机使用一个数据库,在用户的db表的Host条目设为空值,然后将那些主机的每一个移入host表。这个机制详细描述在6.8 存取控制, 阶段2:请求证实。
tables_priv和columns_priv表类似于db表,但是更精致:他们在表和列级应用而非在数据库级。
注意管理权限(reload, shutdown, 等等)仅在user表中被指定。这是因为管理性操作是服务器本身的操作并且不是特定数据库,因此没有理由在其他授权表中列出这样的权限。事实上,只需要请教user表来决定你是否执行一个管理操作。
file权限也仅在user表中指定。它不是管理性权限,但你读或谢在服务器主机上的文件的的能力独立于你正在存取的数据库。
当mysqld服务器启动时,读取一次授权表内容。对授权表的更改生效在6.9 权限更改何时生效描述。
当你修改授权表的内容时,确保你按你想要的方式更改权限设置是一个好主意。为帮助诊断问题,见6.13 “存取拒绝引起”错误的原因。对于安全问题上的忠告,见6.14 怎么对使MySQL安全对抗解密高手。
一个有用的诊断工具是mysqlaccess脚本,由Carlier Yves 提供给MySQL分发。使用--help选项调用mysqlaccess查明它怎样工作。注意:mysqlaccess仅用user、db和host表仅检查存取。它不检查表或列级权限。
6.7 存取控制, 阶段1:连接证实
当你试图联接一个MySQL服务器时,服务器基于你的身份和你是否能通过供应正确的口令验证身份来接受或拒绝连接。如果不是,服务器完全具结你的存取,否则,服务器接受连接,然后进入阶段2并且等待请求。
你的身份基于2个信息:
你从那个主机连接
你的MySQL用户名
身份检查使用3个user表(Host, User和Password)范围字段执行。服务器只有在一个user表条目匹配你的主机名和用户名并且你提供了正确的口令时才接受连接。
在user表范围字段可以如下被指定:
一个Host值可以是主机名或一个IP数字,或'localhost'指出本地主机。
你可以在Host字段里使用通配符字符“%”和“_”。
一个Host值'%'匹配任何主机名,一个空白Host值等价于'%'。注意这些值匹配能创建一个连接到你的服务器的任何主机!
通配符字符在User字段中不允许,但是你能指定空白的值,它匹配任何名字。如果user表匹配到来的连接的条目有一个空白的用户名,用户被认为是匿名用户(没有名字的用户),而非客户实际指定的名字。这意味着一个空白的用户名被用于在连接期间的进一步的存取检查(即,在阶段2期间)。
Password字段可以是空白的。这不意味着匹配任何口令,它意味着用户必须不指定一个口令进行连接。
非空白Password值代表加密的口令。 MySQL不以任何人可以看的纯文本格式存储口令,相反,正在试图联接的一个用户提供的口令被加密(使用PASSWORD()函数),并且与存储了user表中的已经加密的版本比较。如果他们匹配,口令是正确的。
下面的例子显示出各种user表中Host和User条目的值的组合如何应用于到来的连接:
Host 值 User 值 被条目匹配的连接
'thomas.loc.gov' 'fred' fred, 从thomas.loc.gov 连接
'thomas.loc.gov' '' 任何用户, 从thomas.loc.gov连接
'%' 'fred' fred, 从任何主机连接
'%' '' 任何用户, 从任何主机连接
'%.loc.gov' 'fred' fred, 从在loc.gov域的任何主机连接
'x.y.%' 'fred' fred, 从x.y.net、x.y.com,x.y.edu等联接。(这或许无用)
'144.155.166.177' 'fred' fred, 从有144.155.166.177 IP 地址的主机连接
'144.155.166.%' 'fred' fred, 从144.155.166 C类子网的任何主机连接
既然你能在Host字段使用IP通配符值(例如,'144.155.166.%'匹配在一个子网上的每台主机),有可能某人可能企图探究这种能力,通过命名一台主机为144.155.166.somewhere.com。为了阻止这样的企图,MySQL不允许匹配以数字和一个点起始的主机名,这样,如果你用一个命名为类似1.2.foo.com的主机,它的名字决不会匹配授权表中Host列。只有一个IP数字能匹配IP通配符值。
一个到来的连接可以被在user表中的超过一个条目匹配。例如,一个由fred从thomas.loc.gov的连接匹配多个条目如上所述。如果超过一个匹配,服务器怎么选择使用哪个条目呢?服务器在启动时读入user表后通过排序来解决这个问题,然后当一个用户试图连接时,以排序的顺序浏览条目,第一个匹配的条目被使用。
user表排序工作如下,假定user表看起来像这样:
+-----------+----------+-
| Host | User | ...
+-----------+----------+-
| % | root | ...
| % | jeffrey | ...
| localhost | root | ...
| localhost | | ...
+-----------+----------+-
当服务器在表中读取时,它以最特定的Host值为先的次序排列('%'在Host列里意味着“任何主机”并且是最不特定的)。有相同Host值的条目以最特定的User值为先的次序排列(一个空白User值意味着“任何用户”并且是最不特定的)。最终排序的user表看起来像这样:
+-----------+----------+-
| Host | User | ...
+-----------+----------+-
| localhost | root | ...
| localhost | | ...
| % | jeffrey | ...
| % | root | ...
+-----------+----------+-
当一个连接被尝试时,服务器浏览排序的条目并使用找到的第一个匹配。对于由jeffrey从localhost的一个连接,在Host列的'localhost'条目首先匹配。那些有空白用户名的条目匹配连接的主机名和用户名。('%'/'jeffrey'条目也将匹配,但是它不是在表中的第一匹配。)
这是另外一个例子。假定user桌子看起来像这样:
+----------------+----------+-
| Host | User | ...
+----------------+----------+-
| % | jeffrey | ...
| thomas.loc.gov | | ...
+----------------+----------+-
排序后的表看起来像这样:
+----------------+----------+-
| Host | User | ...
+----------------+----------+-
| thomas.loc.gov | | ...
| % | jeffrey | ...
+----------------+----------+-
一个由jeffrey从thomas.loc.gov的连接被第一个条目匹配,而一个由jeffrey从whitehouse.gov的连接被第二个匹配。
普遍的误解是认为,对一个给定的用户名,当服务器试图对连接寻找匹配时,明确命名那个用户的所有条目将首先被使用。这明显不是事实。先前的例子说明了这点,在那里一个由jeffrey从thomas.loc.gov的连接没被包含'jeffrey'作为User字段值的条目匹配,但是由没有用户名的题目匹配!
如果你有服务器连接的问题,打印出user表并且手工排序它看看第一个匹配在哪儿进行。
6.8 存取控制,阶段2:请求证实
一旦你建立了一个连接,服务器进入阶段2。对在此连接上进来的每个请求,服务器检查你是否有足够的权限来执行它,它基于你希望执行的操作类型。这正是在授权表中的权限字段发挥作用的地方。这些权限可以来子user、db、host、tables_priv或columns_priv表的任何一个。授权表用GRANT和REVOKE命令操作。见7.26 GRANT和REVOKE 句法。(你可以发觉参考6.6 权限系统怎样工作很有帮助,它列出了在每个权限表中呈现的字段。)
user表在一个全局基础上授予赋予你的权限,该权限不管当前的数据库是什么均适用。例如,如果user表授予你delete权限, 你可以删除在服务器主机上从任何数据库删除行!换句话说,user表权限是超级用户权限。只把user表的权限授予超级用户如服务器或数据库主管是明智的。对其他用户,你应该把在user表中的权限设成'N'并且仅在一个特定数据库的基础上授权, 使用db和host表。
db和host表授予数据库特定的权限。在范围字段的值可以如下被指定:
通配符字符“%”和“_”可被用于两个表的Host和Db字段。
在db表的'%'Host值意味着“任何主机”,在db表中一个空白Host值意味着“对进一步的信息咨询host表”。
在host表的一个'%'或空白Host值意味着“任何主机”。
在两个表中的一个'%'或空白Db值意味着“任何数据库”。
在两个表中的一个空白User值匹配匿名用户。
db和host表在服务器启动时被读取和排序(同时它读user表)。db表在Host、Db和User范围字段上排序,并且host表在Host和Db范围字段上排序。对于user表,排序首先放置最特定的值然后最后最不特定的值,并且当服务器寻找匹配入条目时,它使用它找到的第一个匹配。
tables_priv和columns_priv表授予表和列特定的权限。在范围字段的值可以如下被指定:
通配符“%”和“_”可用在使用在两个表的Host字段。
在两个表中的一个'%'或空白Host意味着“任何主机”。
在两个表中的Db、Table_name和Column_name字段不能包含通配符或空白。
tables_priv和columns_priv表在Host、Db和User字段上被排序。这类似于db表的排序,尽管因为只有Host字段可以包含通配符,但排序更简单。
请求证实进程在下面描述。(如果你熟悉存取检查的源代码,你会注意到这里的描述与在代码使用的算法略有不同。描述等价于代码实际做的东西;它只是不同于使解释更简单。)
对管理请求(shutdown、reload等等),服务器仅检查user表条目,因为那是唯一指定管理权限的表。如果条目许可请求的操作,存取被授权了,否则拒绝。例如,如果你想要执行mysqladmin shutdown,但是你的user表条目没有为你授予shutdown权限,存取甚至不用检查db或host表就被拒绝。(因为他们不包含Shutdown_priv行列,没有这样做的必要。)
对数据库有关的请求(insert、update等等),服务器首先通过查找user表条目来检查用户的全局(超级用户)权限。如果条目允许请求的操作,存取被授权。如果在user表中全局权限不够,服务器通过检查db和host表确定特定的用户数据库权限:
服务器在db表的Host、Db和User字段上查找一个匹配。 Host和User对应连接用户的主机名和MySQL用户名。Db字段对应用户想要存取的数据库。如果没有Host和User的条目,存取被拒绝。
如果db表中的条目有一个匹配而且它的Host字段不是空白的,该条目定义用户的数据库特定的权限。
如果匹配的db表的条目的Host字段是空白的,它表示host表列举主机应该被允许存取数据库的主机。在这种情况下,在host表中作进一步查找以发现Host和Db字段上的匹配。如果没有host表条目匹配,存取被拒绝。如果有匹配,用户数据库特定的权限以在db和host表的条目的权限,即在两个条目都是'Y'的权限的交集(而不是并集!)计算。(这样你可以授予在db表条目中的一般权限,然后用host表条目按一个主机一个主机为基础地有选择地限制它们。)
在确定了由db和host表条目授予的数据库特定的权限后,服务器把他们加到由user表授予的全局权限中。如果结果允许请求的操作,存取被授权。否则,服务器检查在tables_priv和columns_priv表中的用户的表和列权限并把它们加到用户权限中。基于此结果允许或拒绝存取。
用布尔术语表示,前面关于一个用户权限如何计算的描述可以这样总结:
global privileges
OR (database privileges AND host privileges)
OR table privileges
OR column privileges
它可能不明显,为什么呢,如果全局user条目的权限最初发现对请求的操作不够,服务器以后把这些权限加到数据库、表和列的特定权限。原因是一个请求可能要求超过一种类型的权限。例如,如果你执行一个INSERT ... SELECT语句,你就都要insert和select权限。你的权限必须如此以便user表条目授予一个权限而db表条目授予另一个。在这种情况下,你有必要的权限执行请求,但是服务器不能自己把两个表区别开来;两个条目授予的权限必须组合起来。
host表能被用来维护一个“安全”服务器列表。在TcX,host表包含一个在本地的网络上所有的机器的表,这些被授予所有的权限。
你也可以使用host表指定不安全的主机。假定你有一台机器public.your.domain,它位于你不认为是安全的一个公共区域,你可以用下列的host表条目子允许除了那台机器外的网络上所有主机的存取:
+--------------------+----+-
| Host | Db | ...
+--------------------+----+-
| public.your.domain | % | ... (所有权限设为 'N')
| %.your.domain | % | ... (所有权限设为 'Y')
+--------------------+----+-
当然,你应该总是测试你在授权表中的条目(例如,使用mysqlacces