5.7. MySQL访问权限系统
- 5.7.1. 权限系统的作用
- 5.7.2. 权限系统工作原理
- 5.7.3. MySQL提供的权限
- 5.7.4. 与MySQL服务器连接
- 5.7.5. 访问控制, 阶段1:连接核实
- 5.7.6. 访问控制, 阶段2:请求核实
- 5.7.7. 权限更改何时生效
- 5.7.8. 拒绝访问错误的原因
- 5.7.9. MySQL 4.1中的密码哈希处理
5.7.1. 权限系统的作用
MySQL权限系统的主要功能是证实连接到一台给定主机的用户,并且赋予该用户在数据库上的SELECT、INSERT、UPDATE和DELETE权限。附加的功能包括有匿名的用户并对于MySQL特定的功能例如LOAD DATA INFILE进行授权及管理操作的能力。
5.7.2. 权限系统工作原理
MySQL权限系统保证所有的用户只执行允许做的事情。当你连接MySQL服务器时,你的身份由你从那儿连接的主机和你指定的用户名来决定。连接后发出请求后,系统根据你的身份和你想做什么来授予权限。
MySQL在认定身份中考虑你的主机名和用户名字,是因为几乎没有原因假定一个给定的用户在因特网上属于同一个人。例如,从office.com连接的用户joe不一定和从elsewhere.com连接的joe是同一个人。MySQL通过允许你区分在不同的主机上碰巧有同样名字的用户来处理它:你可以对joe从office.com进行的连接授与一个权限集,而为joe从elsewhere.com的连接授予一个不同的权限集。
MySQL存取控制包含2个阶段:
- 阶段1:服务器检查是否允许你连接。
- 阶段2:假定你能连接,服务器检查你发出的每个请求。看你是否有足够的权限实施它。例如,如果你从数据库表中选择(select)行或从数据库删除表,服务器确定你对表有SELECT权限或对数据库有DROP权限。
如果连接时你的权限被更改了(通过你和其它人),这些更改不一定立即对你发出的下一个语句生效。详情参见5.7.7节,“权限更改何时生效”。
服务器在mysql数据库的授权表中保存权限信息(即在mysql数据库中)。当MySQL服务器启动时将这些表的内容读入内存,在5.7.7节,“权限更改何时生效”的环境下重新读取它们。访问控制决策取决于内存中的授权表的份数。
一般情况,你通过GRANT和REVOKE语句间接来操作授权表的内容,设置账户并控制个人的权限。参见13.5.1.3节,“GRANT和REVOKE语法”。下面讨论了授权表的结构以及服务器与客户端交互操作时如何使用其内容。
服务器在存取控制的两个阶段使用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 | |
Create_view_priv | Create_view_priv | Create_view_priv | |
Show_view_priv | Show_view_priv | Show_view_priv | |
Create_routine_priv | Create_routine_priv | ||
Alter_routine_priv | Alter_routine_priv | ||
References_priv | References_priv | References_priv | |
Reload_priv | |||
Shutdown_priv | |||
Process_priv | |||
File_priv | |||
Show_db_priv | |||
Super_priv | |||
Create_tmp_table_priv | Create_tmp_table_priv | Create_tmp_table_priv | |
Lock_tables_priv | Lock_tables_priv | Lock_tables_priv | |
Execute_priv | |||
Repl_slave_priv | |||
Repl_client_priv | |||
安全列 | ssl_type | ||
ssl_cipher | |||
x509_issuer | |||
x509_subject | |||
资源控制列 | max_questions | ||
max_updates | |||
max_connections | |||
max_user_connections |
对存取控制的第二阶段(请求证实),服务器执行请求验证以确保每个客户端有充分的权限满足各需求。除了user、db和host授权表,如果请求涉及表,服务器可以另外参考tables_priv和columns_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 |
Timestamp和Grantor列当前还未使用,这儿不再进一步讨论。
为了对涉及保存程序的请求进行验证,服务器将查阅procs_priv表。该表具有以下列:
表名 | procs_priv |
范围列 | Host |
Db | |
User | |
Routine_name | |
Routine_type | |
权限列 | Proc_priv |
其它列 | Timestamp |
Grantor |
Routine_type列为ENUM列,值为'FUNCTION'或'PROCEDURE',表示行所指的程序类型。该列允许为同名函数和程序单独授权。
Timestamp和Grantor列当前还未使用,这儿不再进一步讨论。
每个授权表包含范围列和权限列:
l 范围列决定表中每个条目(行)的范围,即,行适用的上下文。例如,一个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表包含范围列,指出每个行适用的表或表/列的组合。procs_priv范围列指出每个行适用的保存程序。
对于检查存取的用途,比较Host值是忽略大小写的。User、Password、Db和Table_name值是区分大小写的。Column_name值在MySQL3.22.12或以后版本是忽略大小写的。
l 权限列指出由一个表行授予的权限,即,可实施什么操作。服务器组合各种的授权表的信息形成一个用户权限的完整描述。为此使用的规则在5.7.6节,“访问控制, 阶段2:请求核实”描述。
范围列包含字符串,如下所述;每个列的默认值是空字符串:
列名 | 类型 |
Host | CHAR(60) |
User | CHAR(16) |
Password | CHAR(16) |
Db | CHAR(64) |
Table_name | CHAR(64) |
Column_name | CHAR(64) |
Routine_name | CHAR(64) |
为了访问检查目的,Host值的比较对大小写不敏感。User、Password、Db和Table_name值对大小写敏感。Column_name值对大小写不敏感。
在user、db和host表中,所有权限列于单独的列内,被声明为ENUM('N','Y') DEFAULT 'N'。换句话说,每一个权限都可以被禁用和启用,并且默认是禁用。
在tables_priv、columns_priv和procs_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' |
procs_priv | Proc_priv | 'Execute', 'Alter Routine', 'Grant' |
简单地说,服务器使用这样的授权表:
·user表范围列决定是否允许或拒绝到来的连接。对于允许的连接,user表授予的权限指出用户的全局(超级用户)权限。这些权限适用于服务器上的all数据库。
·db表范围列决定用户能从哪个主机存取哪个数据库。权限列决定允许哪个操作。授予的数据库级别的权限适用于数据库和它的表。
·当你想要一个给定的db表行应用于若干主机时,db和host表一起使用。例如,如果你想要一个用户能在你的网络从若干主机使用一个数据库,在用户的db表行的Host值设为空值,然后将那些主机的每一个移入host表。这个机制详细描述在5.7.6节,“访问控制, 阶段2:请求核实”。
注释:host表不受GRANT和REVOKE语句的影响。大多数MySQL安装根本不需要使用该表。
·tables_priv和columns_priv表类似于db表,但是更精致:它们在表和列级应用而非在数据库级。授予表级别的权限适用于表和所有它的列。授予列级别的权限只适用于专用列。
·procs_priv表适用于保存的程序。授予程序级别的权限只适用于单个程序。
管理权限(例如RELOAD或SHUTDOWN等等)仅在user表中被指定。这是因为管理性操作是服务器本身的操作并且不是特定数据库,因此没有理由在其他授权表中列出这样的权限。事实上,只需要查询user表来决定你是否执行一个管理操作。
FILE权限也仅在user表中指定。它不是管理性权限,但你在服务器主机上读或写文件的能力与你正在存取的数据库无关。
当mysqld服务器启动时,将授权表的内容读入到内存中。你可以通过FLUSH PRIVILEGES语句或执行mysqladmin flush-privileges或mysqladmin reload命令让它重新读取表。对授权表的更改生效在5.7.7节,“权限更改何时生效”描述。
当你修改授权表的内容时,确保你按你想要的方式更改权限设置是一个好主意。要检查给定账户的权限,使用SHOW GRANTS语句。例如,要检查Host和User值分别为pc84.example.com和bob的账户所授予的权限,应通过语句:
mysql> SHOW GRANTS FOR 'bob'@'pc84.example.com';
一个有用的诊断工具是mysqlaccess脚本,由Carlier Yves 提供给MySQL分发。使用--help选项调用mysqlaccess查明它怎样工作。注意:mysqlaccess仅用user、db和host表检查存取。它不检查tables_priv、columns_priv或procs_priv表中指定的表、列和程序级权限。
对于诊断权限相关的问题的其它帮助,参见5.7.8节,“拒绝访问错误的原因”。对于安全问题常规建议,参见5.6节,“一般安全问题”。
5.7.3. MySQL提供的权限
账户权限信息被存储在mysql数据库的user、db、host、tables_priv、columns_priv和procs_priv表中。在MySQL启动时并在5.7.7节,“权限更改何时生效”所说的情况时,服务器将这些数据库表内容读入内存。
GRANT和REVOKE语句所用的涉及权限的名称显示在下表,还有在授权表中每个权限的表列名称和每个权限有关的上下文。关于每个权限的含义相关的详细信息参见13.5.1.3节,“GRANT和REVOKE语法”。
权限 | 列 | 上下文 |
CREATE | Create_priv | 数据库、表或索引 |
DROP | Drop_priv | 数据库或表 |
GRANT OPTION | Grant_priv | 数据库、表或保存的程序 |
REFERENCES | References_priv | 数据库或表 |
ALTER | Alter_priv | 表 |
DELETE | Delete_priv | 表 |
INDEX | Index_priv | 表 |
INSERT | Insert_priv | 表 |
SELECT | Select_priv | 表 |
UPDATE | Update_priv | 表 |
CREATE VIEW | Create_view_priv | 视图 |
SHOW VIEW | Show_view_priv | 视图 |
ALTER ROUTINE | Alter_routine_priv | 保存的程序 |
CREATE ROUTINE | Create_routine_priv | 保存的程序 |
EXECUTE | Execute_priv | 保存的程序 |
FILE | File_priv | 服务器主机上的文件访问 |
CREATE TEMPORARY TABLES | Create_tmp_table_priv | 服务器管理 |
LOCK TABLES | Lock_tables_priv | 服务器管理 |
CREATE USER | Create_user_priv | 服务器管理 |
PROCESS | Process_priv | 服务器管理 |
RELOAD | Reload_priv | 服务器管理 |
REPLICATION CLIENT | Repl_client_priv | 服务器管理 |
REPLICATION SLAVE | Repl_slave_priv | 服务器管理 |
SHOW DATABASES | Show_db_priv | 服务器管理 |
SHUTDOWN | Shutdown_priv | 服务器管理 |
SUPER | Super_priv | 服务器管理 |
当从早期的没有CREATE VIEW、SHOW VIEW、CREATE ROUTINE、ALTER ROUTINE和EXECUTE权限的版本的MySQL中升级时,要想使用这些权限,你必须使用MySQL分发提供的mysql_fix_privilege_tables脚本升级授权表。参见2.10.2节,“升级授权表”。
如果启用了二进制记录,要想创建或修改保存的程序,你还需要SUPER权限,详细描述见20.4节,“存储子程序和触发程序的二进制日志功能”。
通过CREATE和DROP权限,你可以创建新数据库和表,或删除(移掉)已有数据库和表。如果你将mysql数据库中的DROP权限授予某用户,用户可以删掉MySQL访问权限保存的数据库。
SELECT、INSERT、UPDATE和DELETE权限允许你在一个数据库现有的表上实施操作。
SELECT语句只有在他们真正从一个表中检索行时才需要SELECT权限。一些SELECT语句不访问表,甚至没有任何到服务器上的数据库里的存取任何东西的许可。例如,你可使用mysql客户端作为一个简单的计算器来评估未引用表的表达式:
mysql> SELECT 1+1;
mysql> SELECT PI()*2;
INDEX权限允许你创建或删除索引。INDEX适用已有表。如果你具有某个表的CREATE权限,你可以在CREATE TABLE语句中包括索引定义。
通过ALTER权限,你可以使用ALTER TABLE来更改表的结构和重新命名表。
需要CREATE ROUTINE权限来创建保存的程序(函数和程序),ALTER ROUTINE权限来更改和删除保存的程序,EXECUTE来执行保存的程序。
GRANT权限允许你把你自己拥有的那些权限授给其他的用户。可以用于数据库、表和保存的程序。
FILE权限给予你用LOAD DATA INFILE和SELECT ... INTO OUTFILE语句读和写服务器上的文件,任何被授予FILE权限的用户都能读或写MySQL服务器能读或写的任何文件。(说明用户可以读任何数据库目录下的文件,因为服务器可以访问这些文件)。FILE权限允许用户在MySQL服务器具有写权限的目录下创建新文件。不能覆盖已有文件。
其余的权限用于管理性操作,它使用mysqladmin程序或SQL语句实施。下表显示每个管理性权限允许你执行的mysqladmin命令:
权限 | 权限拥有者允许执行的命令 |
RELOAD | flush-hosts,flush-logs, flush-privileges, flush-status, flush-tables, flush-threads, refresh,reload |
SHUTDOWN | shutdown |
PROCESS | processlist |
SUPER | kill |
reload命令告诉服务器将授权表重新读入内存。flush-privileges是reload的同义词,refresh命令清空所有表并打开并关闭记录文件,其它flush-xxx命令执行类似refresh的功能,但是范围更有限,并且在某些情况下可能更好用。例如,如果你只是想清空记录文件,flush-logs比refresh是更好的选择。
shutdown命令关掉服务器。只能从mysqladmin发出命令。没有相应的SQL语句。
processlist命令显示在服务器内执行的线程的信息(即其它账户相关的客户端执行的语句)。kill命令杀死服务器线程。你总是能显示或杀死你自己的线程,但是你需要PROCESS权限来显示或杀死其他用户和SUPER权限启动的线程。参见13.5.5.3节,“KILL语法”。拥有CREATE TEMPORARY TABLES权限便可以使用CREATE TABLE语句中的关键字TEMPORARY。
拥有LOCK TABLES权限便可以直接使用LOCK TABLES语句来锁定你拥有SELECT权限的表。包括使用写锁定,可以防止他人读锁定的表。
拥有REPLICATION CLIENT权限便可以使用SHOW MASTER STATUS和SHOW SLAVE STATUS。
REPLICATION SLAVE权限应授予从服务器所使用的将当前服务器连接为主服务器的账户。没有这个权限,从服务器不能发出对主服务器上的数据库所发出的更新请求。
拥有SHOW DATABASES权限便允许账户使用SHOW DATABASE语句来查看数据库名。没有该权限的账户只能看到他们具有部分权限的数据库, 如果数据库用--skip-show-database选项启动,则根本不能使用这些语句。请注意全局权限指数据库的权限。
总的说来,只授予权限给需要他们的那些用户是好主意,但是你应该在授予FILE和管理权限时试验特定的警告:
- FILE权限可以被滥用于将服务器主机上MySQL能读取的任何文件读入到数据库表中。包括任何人可读的文件和服务器数据目录中的文件。可以使用SELECT访问数据库表,然后将其内容传输到客户端上。
- GRANT权限允许用户将他们的权限给其他用户。有不同的权限并有GRANT权限的2个用户可以合并权限。
- ALTER权限可以用于通过重新命名表来推翻权限系统。
- SHUTDOWN权限通过终止服务器可以被滥用完全拒绝为其他用户服务。
- PROCESS权限能被用来察看当前执行的查询的明文文本,包括设定或改变密码的查询。
- SUPER权限能用来终止其它用户或更改服务器的操作方式。
- 授给mysql数据库本身的权限能用来改变密码和其他访问权限信息。密码被加密存储,所以恶意的用户不能简单地读取他们以知道明文密码。然而,具有user表Password列写访问权限的用户可以更改账户的密码,并可以用该账户连接MySQL服务器。
有一些事情你不能用MySQL权限系统做到:
- 你不能明显地指定某个给定的用户应该被拒绝访问。即,你不能明显地匹配用户然后拒绝连接。
- 你不能指定用户有权创建立或删除数据库中的表,但不能创建或删除数据库本身。
5.7.4. 与MySQL服务器连接
当你想要访问MySQL服务器时,MySQL客户端程序一般要求你指定参数:
·MySQL服务器运行的主机名
·姓名
·密码
例如,可以从命令行按照下述提示启动MySQL客户端(用shell>表示):
shell> MySQL -h host_name -u user_name -pyour_pass
-h,-u和-p选项还可以采用形式--host=host_name、--user=user_name和--password=your_pass。请注意在-p或--password=和后面的密码之间没有空格。
如果你使用-p或--password选项但没有指定密码值,客户端程序提示你输入密码。当你输入密码时并不显示密码。这比在在命令行输入密码要安全得多。系统上的任何用户可以通过命令ps auxww在命令行中指定密码。参见5.8.6节,“使你的密码安全”。
如果没有指定连接参数,MySQL客户端程序使用默认值:
- 默认主机名是localhost。
- 默认用户名在Windows中是ODBC,在Unix中是你的Unix登录名。
·如果没有-p,则不提供密码。
这样, 对Unix用户joe,下列命令是等价的:
shell> MySQL -h localhost -u joe
shell> MySQL -h localhost
shell> MySQL -u joe
shell> MySQL
其它MySQL客户端程序类似。
当进行连接时,你可以指定要使用的不同的默认值,这样不必每次在你调用客户端程序是在命令行上输入它们。这可以有很多方法做到:
- 你可以在选项文件的[client]小节里指定连接参数。文件的相关小节看上去可能像这样:
·[client]
·host=host_name
·user=user_name
·password=your_pass
在4.3.2节,“使用选项文件”中详细讨论了选项文件。
- 你可以用环境变量指定连接参数。主机可用MYSQL_HOST指定,MySQL用户名可用USER指定(仅对Windows和NetWare),密码可用MYSQL_PWD指定,尽管这不安全;参见5.8.6节,“使你的密码安全”。变量参见附录F:环境变量。
5.7.5. 访问控制, 阶段1:连接核实
当你试图连接MySQL服务器时,服务器基于你的身份以及你是否能通过供应正确的密码验证身份来接受或拒绝连接。如果不是,服务器完全拒绝你的访问,否则,服务器接受连接,然后进入阶段2并且等待请求。
你的身份基于2个信息:
- 你从那个主机连接
- 你的MySQL用户名
身份检查使用3个user表(Host,User和Password)范围列执行。服务器只有在user表记录的Host和User列匹配客户端主机名和用户名并且提供了正确的密码时才接受连接。
在user表Host值的指定方法:
- Host值可以是主机名或IP号,或'localhost'指出本地主机。
- 你可以在Host列值使用通配符字符“%”和“_”。
- Host值'%'匹配任何主机名,空Host值等价于'%'。它们的含义与LIKE操作符的模式匹配操作相同。例如,'%'的Host值与所有主机名匹配,而'%.mysql.com'匹配mysql.com域的所有主机。
·对于指定为IP号的Host值,你可以指定一个网络掩码,说明使用多少位地址位来评比网络号。例如:
·mysql> GRANT ALL PRIVILEGES ON db.*
· -> -> TO david@'192.58.197.0/255.255.255.0';
允许david从任何客户端用IP号client_ip来连接,下面的条件为真:
client_ip & netmask = host_ip
That is, for theGRANT statement just shown:
client_ip & 255.255.255.0 = 192.58.197.0
满足该条件并可以连接MySQL服务器的IP号的范围为192.58.197.0到192.58.197.255。
·注释:网络掩码只用来告诉服务器使用8、16、24或32位地址,例如:
·192.0.0.0/255.0.0.0(192 A类网络的任何地址)
·192.168.0.0/255.255.0.0(192.168 A类网络的任何地址)
·192.168.1.0/255.255.255.0(192.168.1 C类网络的任何地址)
·192.168.1.1(只有该IP)
下面的网络掩码(28 位)无效:
192.168.0.1/255.255.255.240
·db表记录中的空Host值表示它的权限应结合匹配客户端名的host表中的行使用。通过AND(相与)而不是或(联合)操作将权限组合到一起。你可以从5.7.6节,“访问控制, 阶段2:请求核实”找到关于host表的详细信息。
其它grant表的空Host值与'%'相同。
既然你能在Host字段使用IP通配符值(例如,'144.155.166.%'匹配在一个子网上的每台主机),有可能某人可能企图探究这种能力,通过命名一台主机为144.155.166.somewhere.com。为了阻止这样的企图,MySQL不允许匹配以数字和一个点起始的主机名,这样,如果你用一个命名为类似1.2.foo.com的主机,它的名字决不会匹配授权表中的Host列。只有一个IP数字能匹配IP通配符值。
通配符字符在User列中不允许,但是你能指定空的值,它匹配任何名字。如果user表匹配的连接有一个空用户名,用户被认为是匿名用户(没有名字的用户),而非客户端实际指定的名字。这意味着一个空的用户名被用于在连接期间的进一步的访问检查(即,在阶段2期间)。
Password列可以是空的。这不是通配符,也不意味着匹配任何密码,它意味着用户必须不指定一个密码进行连接。
user表中的非空Password值代表加密的密码。MySQL不以任何人可以看的明文文本格式存储密码,相反,正在试图联接的用户提供的密码被加密(使用PASSWORD( )函数),在连接过程中使用加密的密码检查密码是否正确。(加密后的密码未通过连接即可实现)。从MySQL角度,加密的密码是实际密码,因此你不应让其它人访问它!特别是,绝对不要让非管理用户读mysql数据库中的表!
MySQL 5.1使用强鉴定方法(最先在MySQL 4.1中适用)在前面的版本中在连接进程中的密码保护较好。即使TCP/IP包被截取或mysql数据库 被捕获也很安全。5.7.9节,“MySQL 4.1中的密码哈希处理”中详细讨论了密码加密。
下面的例子显示出各种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类子网的任何主机连接 |
到来的连接中的客户端名和用户名可能与user表中的多行匹配。例如,由fred从thomas.loc.gov的连接匹配多个条目如上所述。
如果有多个匹配,服务器必须选择使用哪个条目。按照下述方法解决问题:
l 服务器在启动时读入user表后进行排序。
l 然后当用户试图连接时,以排序的顺序浏览条目
l 服务器使用与客户端和用户名匹配的第一行。
user表排序工作如下,假定user表看起来像这样:
+-----------+----------+-
| Host | User | …
+-----------+----------+-
| % | root | …
| % | jeffrey | …
| localhost | root | …
| localhost | | …
+-----------+----------+-
当服务器读取表时,它首先以最具体的Host值排序。主机名和IP号是最具体的。'%'意味着“任何主机”并且是最不特定的。有相同Host值的条目首先以最具体的User值排序(空User值意味着“任何用户”并且是最不特定的)。最终排序的user表看起来像这样:
+-----------+----------+-
| Host | User | …
+-----------+----------+-
| localhost | root | … ...
| localhost | | … ...
| % | jeffrey | … ...
| % | root | … ...
+-----------+----------+-
当客户端试图连接时,服务器浏览排序的条目并使用找到的第一匹配。对于由jeffrey从localhost的连接,表内有两个条目匹配:Host和User值为'localhost'和''的条目,和值为'%'和'jeffrey'的条目。'localhost'条目首先匹配,服务器可以使用。
还有一个例子。假定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列值的行匹配,但是由没有用户名的题目匹配!结果是,jeffrey被鉴定为匿名用户,即使他连接时指定了用户名。
如果你能够连接服务器,但你的权限不是你期望的,你可能被鉴定为其它账户。要想找出服务器用来鉴定你的账户,使用CURRENT_USER()函数。它返回user_name@host_name格式的值,说明User和Host值匹配user表记录。假定jeffrey连接并发出下面的查询:
mysql> SELECT CURRENT_USER();
+----------------+
| CURRENT_USER() |
+----------------+
| @localhost |
+----------------+
这儿显示的结果说明user表行有空的User列值。换句话说,服务器将jeffrey视为匿名用户。
诊断鉴定问题的另一个方法是打印出user表并且手动排序它看看第一个匹配在哪儿进行。又见12.9.3节,“信息函数”。
5.7.6. 访问控制, 阶段2:请求核实
一旦你建立了连接,服务器进入访问控制的阶段2。对在此连接上进来的每个请求,服务器检查你想执行什么操作,然后检查是否有足够的权限来执行它。这正是在授权表中的权限列发挥作用的地方。这些权限可以来自user、db、host、tables_priv或columns_priv表。(你会发现参考5.7.2节,“权限系统工作原理”很有帮助,它列出了每个授权表中呈现的列。)
user表在全局基础上授予赋予你的权限,该权限不管当前的数据库是什么均适用。例如,如果user表授予你DELETE权限, 你可以删除在服务器主机上从任何数据库删除行!换句话说,user表权限是超级用户权限。只把user表的权限授予超级用户如服务器或数据库主管是明智的。对其他用户,你应该把在user表中的权限设成'N'并且仅在特定数据库的基础上授权。你可以为特定的数据库、表或列授权。
db和host表授予数据库特定的权限。在这些表中的范围列的值可以采用以下方式:
- 通配符字符“%”并“_”可用于两个表的Host和Db列。它们与用LIKE操作符执行的模式匹配操作具有相同的含义。如果授权时你想使用某个字符,必须使用反斜现引用。例如,要想在数据库名中包括下划线(‘_’),在GRANT语句中用‘\_’来指定。
- 在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表行没有为你授予HUTDOWN权限,甚至不用检查db或host表就拒绝你的访问。(因为它们不包含hutdown_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 INTO ... SELECT语句,你就需要INSERT和SELECT权限。你的权限必须是user表行授予一个权限而db表行授予另一个权限。在这种情况下,你有必要的权限执行请求,但是服务器不能自己把两个表区别开来;两个行授予的权限必须组合起来。
host表不受GRANT或REVOKE语句的影响,因此在大多数MySQL安装中没有使用。如果你直接修改它,你可以用于某种专门目的,例如用来维护安全服务器列表。例如,在TcX,host表包含在本地网络上所有的机器的表。这些表被授予所有的权限。
你也可以使用host表指定不安全的主机。假定你有一台机器public.your.domain,它位于你认为不安全的公共区域,你可以用下列的host表条目允许除了那台机器外的网络上所有主机的访问:
+--------------------+----+-
| Host | Db | ...
+--------------------+----+-
| public.your.domain | % | ... (all privileges set to 'N')
| %.your.domain | % | ... (all privileges set to 'Y')
+--------------------+----+-
当然,一定要测试授权表中的行(例如,使用SHOW GRANTS或mysqlaccess),确保你的访问权限实际按你期望的方式被设置。
5.7.7. 权限更改何时生效
当mysqld启动时,所有授权表的内容被读进内存并且从此时生效。
当服务器注意到授权表被改变了时,现存的客户端连接有如下影响:
- 表和列权限在客户端的下一次请求时生效。
- 数据库权限改变在下一个USEdb_name命令生效。
·全局权限的改变和密码改变在下一次客户端连接时生效。
如果用GRANT、REVOKE或SET PASSWORD对授权表进行修改,服务器会注意到并立即重新将授权表载入内存。
如果你手动地修改授权表(使用INSERT、UPDATE或DELETE等等),你应该执行mysqladmin flush-privileges或mysqladmin reload告诉服务器再装载授权表,否则你的更改将不会生效,除非你重启服务器。
如果你直接更改了授权表但忘记重载,重启服务器后你的更改方生效。这样可能让你迷惑为什么你的更改没有什么变化!
5.7.8. 拒绝访问错误的原因
当你试着联接MySQL服务器时,如果碰到问题,下面各项可以帮助你纠正问题:
·确保服务器在运行。如果服务器没有运行,则你不能连接服务器。如果你视图连接服务器并看到下述消息,可能是服务器没有运行:
·shell> mysql
·ERROR 2003: Can't connect to MySQL server on 'host_name' (111)
·shell> mysql
·ERROR 2002: Can't connect to local MySQL server through socket
·'/tmp/mysql.sock' (111)
也可能服务器正在运行,但你可能使用与服务器上侦听的不一样的TCP/IP端口、命名管道或Unix套接字文件。你可以调用客户端程序,指定端口选项来指示正确的端口或套接字选项来指示正确的命名管道或Unix套接字文件。要找出套接字文件的地点,应:
shell> netstat -ln | grep mysql
- 必须正确设置授权表,以便服务器可以使用它们进行访问控制。对于某些分发版类型(例如Windows中的二进制分发版或Linux中的RPM分发版),安装过程初始化包含授权表的mysql数据库。如果分发版没有这样做,你必须运行mysql_install_db脚本来手动初始化授权表。详细内容参见2.9.2节,“Unix下安装后的过程”。
确定是否要初始化授权表的一个方法是寻找数据目录下的mysql目录(数据目录名通常为data或var,位于MySQL安装目录下)。应保证MySQL数据库目录有文件“user.MYD”。否则,执行mysql_install_db脚本。运行并重启服务器后,执行该命令来测试初始权限:
shell> mysql -u root test
服务器应该让你无误地连接。
- 在新的安装以后,你应该连接服务器并且设置你的用户及其访问许可:
·shell> mysql -u root mysql
服务器应该让你连接,因为MySQLroot用户初始时没有密码。那也是安全风险,当你正在设置其他MySQL用户时,也应设定root密码是一件重要的事请。关于设置初始密码的说明,参见2.9.3节,“使初始MySQL账户安全”。
- 如果你将一个现存的MySQL安装升级到较新的版本,运行了mysql_fix_privilege_tables脚本吗?如果没有,运行它。增加新功能后,授权表的结构可能会改变,因此更新后应确保表的结构随之更新。相关说明参见2.10.2节,“升级授权表”。
·如果客户端程序试图连接时收到以下错误信息,说明服务器需要新格式的密码,而客户端不能生成:
·shell> mysql
·Client does not support authentication protocol requested
·by server; consider upgrading MySQL client
关于如何处理的详细信息,参见5.7.9节,“MySQL 4.1中的密码哈希处理”和A.2.3节,“客户端不支持鉴定协议”。
- 如果你作为root试试连接并且得到这个错误,这意味着,你没有行在user表中的User列值为'root'并且mysqld不能为你的客户端解析主机名:
- Access denied for user ''@'unknown' to database mysql
在这种情况下,你必须用--skip-grant-tables选项重启服务器并且编辑“/etc/hosts”或“\windows\hosts”文件为你的主机增加行。
- 如果你从3.22.11以前的版本更新现存的MySQL安装到3.22.11版或以后版本,你运行了mysql_fix_privilege_tables脚本吗?如果没有,运行它。在GRANT语句变得能工作时,授权表的结构用MySQL 3.22.11修改 。
·记住客户端程序使用选项文件或环境变量中指定的连接参数。如果客户端程序发送不正确的默认连接参数,而你没有在命令行中指定,检查环境变量和适用的选项文件。例如,当你不用任何选项运行客户端程序,得到Access denied错误,确保你没有在选项文件中指定旧密码!
你可以通过使用--no-defaults选项调用客户端程序来禁用选项文件。例如:
shell> mysqladmin --no-defaults -u root version
客户端使用的选项文件见4.3.2节,“使用选项文件”。环境变量列于附录F:环境变量。
·如果遇到下述错误,说明root密码错误:
·shell> mysqladmin -u root -pxxxx ver
·Access denied for user 'root'@'localhost' (using password: YES)
如果你未指定密码时出现前面的错误,说明某个选项文件中的密码不正确。试试前面所说的--no-defaults选项。
关于密码更改的信息参见5.8.5节,“设置账户密码”。
如果你丢失或忘记root密码,你可以用--skip-grant-tables重启 mysqld来更改密码。参见A.4.1节,“如何复位根用户密码”.
·如果你使用SET PASSWORD、INSERT或UPDATE更改密码,你必须使用 PASSWORD()函数加密密码。如果你不使用PASSWORD()函数,密码不工作。例如,下面的语句设置密码,但没能加密,因此用户后面不能连接:
·mysql> SET PASSWORD FOR 'abe'@'host_name' = 'eagle';
相反,应这样设置密码:
mysql> SET PASSWORD FOR 'abe'@'host_name' = PASSWORD('eagle');
当你使用GRANT或CREATE USER语句或mysqladmin password命令指定密码时,不需要PASSWORD()函数,它们会自动使用PASSWORD()来加密密码。参见5.8.5节,“设置账户密码”和13.5.1.1节,“CREATE USER语法”。
·localhost是你本地主机名的一个同义词,并且也是如果你不明确地指定主机而客户端尝试连接的默认主机。
要想在这种系统上避免该问题,你可以使用--host=127.0.0.1选项来明确命名服务器主机。这样将通过TCP/IP协议来连接本地mysqld服务器。你还可以指定--host选项使用TCP/IP,使用实际的本机主机名。在这种情况下,主机名必须指定为服务器主机上的user表行,即使你在服务器上运行客户端程序。
·当尝试用mysql -u user_name与数据库连接时,如果你得到一个Access denied错误,可能会遇到与user表有关的问题,通过执行mysql -u root mysql并且执行下面的SQL语句进行检查:
·mysql> SELECT * FROM user;
结果应该包含一个有Host和User列的行匹配你的计算机主机名和你的MySQL用户名。
- Access denied错误消息将告诉你,你正在用哪个用户尝试登录,你正在试图连接哪个主机,是否使用了密码。通常,你应该在user表中有一行,正确地匹配在错误消息给出的主机名和用户名。例如,如果遇到包含using password: NO的错误信息,说明你登录时没有密码。
·如果当你试着从一个不是MySQL服务器正在运行的主机上连接时,遇到下列错误,那么在user表中没有匹配那台主机的行:
·Host ... is not allowed to connect to this MySQL server
可以通过组合你正在试图连接的用户/主机名设置一个账户来修正它。如果你不知道正连接的机器的IP号或主机名,应该把一个'%'行作为Host列值放在user表中。在试图从客户端器连接以后,通过SELECT USER()查询显示你如何真正进行连接。(然后用在日志文件上面显示出的实际的主机名代替user表中的'%'行。否则,你将得到一个不安全的系统,因为它允许从任何主机上以任何用户名连接。)
在Linux中,发生该错误的另一个原因可能是你正使用于你所使用版本的glibc库不同版本的库编译的二进制MySQL版本。在这种情况下,你应升级操作系统或glibc,或下载MySQL版本的源码分发版并自己编译。源码RPM一般很容易编译并安装,因此不是大问题。
·如果你连接时指定主机名,但得到错误消息主机名未显示或为IP号,表示当MySQL服务器将IP号解析为客户端来名时遇到错误:
·shell> mysqladmin -u root -pxxxx -h some-hostname ver
·Access denied for user 'root'@'' (using password: YES)
这表示DNS问题。要想修复,执行mysqladmin flush-hosts来重设内部 DNS主机名缓存。参见7.5.6节,“MySQL如何使用DNS”。
一些常用的解决方案包括:
o 试试找出DNS服务器的错误并修复。
o 在MySQL授权表中指定IP号而不是主机名。
o 在/etc/hosts中放入客户端名。
o 用--skip-name-resolve选项启动mysqld。
o 用--skip-host-cache选项启动mysqld。
o 在Unix中,如果你在同一台机器上运行服务器和客户端,连接到localhost。连接到的localhost的Unix连接使用Unix套接字文件而不是TCP/IP。
o 在Windows中,你在同一台机器上运行服务器和客户端并且服务器支持命名管道连接,连接主机名(周期)。连接使用命名管道而不是TCP/IP。
- 如果mysql -u root test工作但是mysql -h your_hostname -u root test导致Access denied(your_hostname是本地机的实际主机名),那么在user表中可能没有你的主机的正确名字。这里的一个普遍的问题是在user表行中的Host值指定一个唯一的主机名,但是你系统的名字解析例程返回一个完全正规的域名(或相反)。例如,如果你在user表中有一个主机是'tcx'的行,但是你的DNS告诉MySQL你的主机名是'tcx.subnet.se',行将不工作。尝试把一个行加到user表中,它包含你主机的IP号作为Host列的值。(另外,你可以把一个行加到user表中,它有包含一个通配符如'tcx.%'的Host值。然而,使用以“%”结尾的主机名是不安全的并且不推荐!)
- 如果mysql -u user_name test工作但是mysql -u user_name other_db_name不工作,你没有为给定的用户授予other_db_name数据库的访问权限。
- 当在服务器上执行mysql -u user_name时,它工作,但是在其它远程客户端上执mysql -h host_name -u user_name时,它却不工作,你没有为给定的用户授予从远程主机访问服务器的权限。
- 如果你不能弄明白你为什么得到Access denied,从user表中删除所有Host包含通配符值的行(包含“%”或“_”的条目)。一个很普遍的错误是用Host='%'和User='some_user'插入一个新行,认为这将允许你指定localhost从同一台机器进行连接。它不工作的原因是默认权限包括一个有Host='localhost'和User=''的行,因为那个行的Host值'localhost'比'%'更具体,当从localhost连接时,它用于指向新行!正确的步骤是插入Host='localhost'和User='some_user'的第2个行,或删除Host='localhost'和User=''行。删除条目后,记住用FLUSH PRIVILEGES语句重载授权表。
·如果你得到下列错误,可以与db或host表有关:
·Access to database denied
如果从db表中选择了在Host列有空值的条目,保证在host表中有一个或多个相应的条目,指定db表中的条目适用哪些主机。
·如果你能够连接MySQL服务器,但如果在使用命令SELECT ... INTO OUTFILE或LOAD DATA INFILE语句时,你得到Access denied错误,在user表中的条目可能没有启用FILE权限。
·如果你直接更改授权表(例如,使用INSERT、UPDATE或DELETE语句)并且你的更改好像被忽略了,记住你必须执行FLUSH PRIVILEGES语句或mysqladmin flush-privileges命令让服务器来重读授权表。否则,直到服务器下次重启,你的更改方有效。记住用UPDATE命令更改root密码后,在清空权限前,你不需要指定新密码,因为服务器还不知道你已经更改了密码!
·如果你的权限似乎在一个会话过程中改变了,可能是一个超级用户改变了他们。再次装入授权表会影响新客户端连接,但是它也影响现存的连接,如5.7.7节,“权限更改何时生效”小节所述。
·如果你有Perl、Python或ODBC程序的存取问题,试着用mysql -u user_name db_name或mysql -u user_name -pyour_pass db_name与服务器连接。如果你能用mysql客户端进行连接,这是程序的一个问题而不是访问权限的问题。(注意在-p和密码之间没有空格;也可以使用--password=your_pass语法指定密码。如果使用-p选项,MySQL提示你输入密码。)
·为了测试,用--skip-grant-tables选项启动mysqld守护进程,然后你可以改变MySQL授权表并且使用mysqlaccess脚本检查你的修改是否有如期的效果。当你对你的改变满意时,执行mysqladmin flush-privileges告诉mysqld服务器开始使用新的授权表。(再次装入授权表覆盖了--skip-grant-tables选项。这允许你告诉服务器开始使用授权表,而不用停掉并重启它)。
·如果任何其它事情失败,用调试选项(例如,--debug=d,general,query)启动mysqld服务器。这将打印有关尝试连接的主机和用户信息,和发出的每个命令的信息。请参见E.1.2节,“创建跟踪文件”。
·如果你有任何与MySQL授权表的其它问题,而且觉得你必须将这个问题发送到邮件表,一定要提供一个MySQL授权表的倾倒副本(dump)。你可用mysqldump mysql命令复制数据库表。象平时一样,用mysqlbug脚本邮寄你的问题。参见1.7.1.3节,“如何通报缺陷和问题”。在一些情况下可以用--skip-grant-tables重启mysqld以便能运行mysqldump。
5.7.9. MySQL 4.1中的密码哈希处理
- 5.7.9.1. 更改应用程序密码哈希值的含义
MySQL用户账户列于mysql数据库中的user表内。每个MySQL账户指定一个密码,尽管保存在user表Password列的密码不是明文,但哈希值是从表中的记录计算的。用PASSWORD()函数来计算密码的哈希值。
MySQL在客户端/服务器通信的两个阶段使用密码:
·如果客户端试图连接服务器,有一个初始鉴定步骤,客户必须提供一个密码,并且必须与客户想要使用的账户在user表保存的哈希值匹配。
·客户端连接后,它可以(如果有充分的权限)设置或更改user表内所列的账户的密码哈希值值。客户端可以通过PASSWORD()函数来生成密码哈希值,或使用GRANT或SET PASSWORD语句。
换句话说,当客户端首次试图连接时,服务器使用哈希值进行鉴定。如果连接的客户端调用PASSWORD()函数或使用GRANT或SET语句来设置或更改密码,则服务器产生哈希值。
在MySQL 4.1中密码哈希算法已经更新,提供了更好的安全性并降低了密码被截取的风险。但是,该新机制只能在MySQL 4.1(和更新版本的)服务器和客户端中使用,会产生一些兼容性问题。4.1或新客户端可以连接4.1之前的服务器,因为客户端可以同时理解旧的和新的密码哈希机制。但是,4.1之前的客户端试图连接4.1版或更新版的服务器时会遇到困难。例如,3.23版mysql客户端试图连接5.1服务器时会失败并出现下面的错误消息:
shell> mysql -h localhost -u root
Client does not support authentication protocol requested
by server; consider upgrading MySQL client
出现该问题的另一个普通例子是在升级到MySQL 4.1或更新版后,试图使用旧版本的PHP mysql扩展名。(参见25.3.1节,“使用MySQL和PHP的常见问题”)。
下面讨论了新、旧密码机制之间的差别,以及如果你升级了服务器但需要为4.1版以前的客户端保持向后兼容性该怎样做。A.2.3节,“客户端不支持鉴定协议”中有更详细的信息。该信息将MySQL数据库从4.0版本或更低版升级到4.1版或更高版的PHP编程人员特别重要。
注释:该讨论对比了4.1版的行为和4.1前的行为,这儿描述的4.1中的行为实际上从4.1.1开始。MySQL 4.1.0是一个“旧”的发布,因为它的实施机制与4.1.1版和更新版中的稍有不同。在MySQL 5.0 参考手册中详细描述了4.1.0和最新版之间的差别。
在MySQL 4.1之前,用PASSWORD()函数计算的密码哈希值有16个字节长。应为:
mysql> SELECT PASSWORD('mypass');
+--------------------+
| PASSWORD('mypass') |
+--------------------+
| 6f8c114b58f2ce9e |
+--------------------+
在MySQL 4.1之前,user表的Password列(保存了哈希值)也是16字节长。
在MySQL 4.1中,已经对PASSWORD()函数进行了修改,可以生成41字节的哈希值:
mysql> SELECT PASSWORD('mypass');
+-------------------------------------------+
| PASSWORD('mypass')|
+-------------------------------------------+
| *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 |
+-------------------------------------------+
同样,user表的Password列必须有41字节长来保存这些值:
·如果你新安装MySQL 5.1, Password列自动为41字节长。
·从MySQL 4.1(4.1.1或4.1系列的更新版)升级到MySQL 5.1,应不会出现相关问题,因为两个版本使用相同的密码哈希机制。如果你想要将更早版本的MySQL升级到MySQL5.1,你应先升级到4.1,然后将4.1升级到5.1。
加宽的Password列可以同时保存新、旧格式的密码哈希值。可以有两种方式确定任何给定格式的密码哈希值:
·明显的不同之处是长度(16字节和41字节)。
·第2个不同之处是新格式的密码哈希值都以‘*’字符开头,而旧格式的密码绝对不是。
长密码哈希值具有更好的加密属性,并且客户端根据长哈希值进行鉴定比旧的短哈希值更加安全。
短密码哈希值和长密码哈希值之间的不同之处与服务器如何使用密码进行鉴定以及如何为执行密码更改操作的连接的客户端生成密码哈希值都有关。
服务器使用密码哈希值进行鉴定的方式受Password列的宽度影响:
·如果列较短,只用短哈希鉴定。
·如果列较长,可以有短或长哈希值,并且服务器可以使用任何一种格式:
o4.1之前的客户端可以连接,它们只可以使用旧的哈希机制,它们可以只鉴定有短哈希的账户。
o4.1及以后版本的客户端可以鉴定有短哈希或长哈希的账户。
对于短哈希账户的鉴定过程,4.1和以后版本的客户端比为旧版本的客户端实际要安全得多。从安全性角度,从最低安全到最安全的梯度为:
·4.1之前的客户端用短密码哈希值进行鉴定
·4.1或以后版本的客户端用短密码哈希值进行鉴定
·4.1或以后版本的客户端用长密码哈希值进行鉴定
服务器为连接的客户端生成密码哈希值的方式受Password列宽度和--old-passwords选项的影响。4.1或更新版本的服务器只有满足某个条件才生成长哈希:Password列必须足够宽以容纳长哈希值并且未给定--old-passwords选项。这些条件适合:
·Password列必须足够宽以容纳长哈希(41字节)值。如果列没有更新,仍然为4.1之前的16字节宽,当客户端使用PASSWORD()、GRANT或SET PASSWORD执行密码更改操作时,服务器注意到长哈希不适合,只生成短哈希。如果你已经升级到4.1但还没有运行 mysql_fix_privilege_tables脚本来扩宽Password列时会出现这种行为。
·如果Password列足够宽,则可以保存短或长密码哈希值。在这种情况下,PASSWORD()、GRANT或SET PASSWORD生成长哈希,除非 用--old-passwords选项启动服务器。该选项强制服务器生成短密码哈希值。
--old-passwords选项的目的是当服务器生成长密码哈希值时,允许你维持同4.1之前的客户端的向后兼容性。该选项不影响鉴定(4.1和以后版本的客户端仍然可以使用有长密码哈希值的账户),但它防止在密码更改操作中在user表中创建长密码哈希值。在这种情况下,该账户不能再用于4.1之前的客户端。没有--old-passwords选项,可能会出现下面的不期望的情况:
·旧客户端连接有短密码哈希值的账户。
·客户更改自己的密码。没有--old-passwords,可以为该账户生成长密码哈希值。
·下次旧客户试图连接账户时不能连接上,因为账户有长密码哈希值,需要新的哈希机制进行鉴定。(一旦账户user表中为长密码哈希值,只有4.1和以后版本的客户端可以鉴定它,因为4.1之前的客户端不理解长哈希)。
该场景说明,如果你必须支持旧的4.1之前的客户端,不使用--old-passwords选项运行4.1或更新版本的服务器很危险。用--old-passwords运行服务器,密码更改操作不会生成长密码哈希值,这样旧客户端也可以访问账户。(这些客户端不能意外地因更改了密码将自己锁出去,并留下长密码哈希值)。
--old-passwords选项的不利之处是你创建或更改的密码使用短哈希,甚至对于4.1客户端也如此。这样,你丢失了长密码哈希值提供的安全性。如果你想要创建有长哈希的账户(例如,为4.1客户端),你必须不使用--old-passwords来运行服务器。
下面的场景可用于运行4.1或以后的服务器,包括MySQL 5.1:
场景1:user表中的短Password列:
·只有短哈希可以保存到Password列。
·服务器只使用短哈希进行客户端鉴定。
·对于连接的客户端,调用PASSWORD()、GRANT或SET PASSWORD的密码哈希生成操作专使用短哈希。对账户的任何更改均会生成短密码哈希值。
· --old-passwords选项可以使用但是多余,因为Password列较短,服务器只生成短密码哈希值。
场景2:长Password列;没有用--old-passwords选项启动服务器:
·短或长哈希可以保存到Password列。
·4.1和以后版本的客户端(包括5.1客户端)可以鉴定有短或长哈希的账户。
·4.1之前的客户端只能鉴定有短哈希的账户。
·对于连接的客户端,调用PASSWORD()、GRANT或SET PASSWORD的密码哈希生成操作专使用短哈希。对账户的任何更改均会生成短密码哈希值。
如前面所示,该场景的危险性在于4.1之前的客户端可能不能访问有短密码哈希值的账户。通过PASSWORD()、GRANT或SET PASSWORDA对这些账户密码的更改会产生长的密码哈希值。从该点看,4.1之前的客户端升级到4.1之前不能鉴定该账户。
要处理该问题,可以用特殊方法更改密码。例如,一般情况你可以使用SET PASSWORD按照下面的方法更改账户密码:
mysql> SET PASSWORD FOR 'some_user'@'some_host' = PASSWORD('mypass');
要想更改密码但创建短哈希,使用OLD_PASSWORD()函数:
mysql> SET PASSWORD FOR 'some_user'@'some_host' = OLD_PASSWORD('mypass');
当你想明显生成短哈希时,OLD_PASSWORD()很有用。
场景3:长Password列;用--old-passwords选项启动4.1或新版本的服务器:
·短或长哈希可以保存到Password列。
·4.1和以后版本的客户端可以鉴定有短或长哈希的账户(请注意只有不使用--old-passwords选项启动服务器,方可以创建长哈希)。
·4.1之前的客户端只可以鉴定短哈希账户。
·对于连接的客户端,调用PASSWORD()、GRANT或SET PASSWORD的密码哈希生成操作专使用短哈希。对账户的任何更改均会生成短密码哈希值。
在该场景中,你不能创建长密码哈希值的账户,因为--old-passwords选项防止生成长哈希。并且,如果你在使用--old-passwords选项前创建长哈希账户,当--old-passwords有时更改账户密码,结果会使账户的密码为短密码,安全性较长哈希要降低。
这些场景的不利之处可以概括为:
在场景1中,你不能利用长哈希提供更安全的鉴定。
在场景2中, 如果你没有显式使用OLD_PASSWORD()来更改密码,则4.1之前的客户端不能再访问短哈希账户。
在场景3中,--old-passwords防止短哈希账户不可访问,但密码更改操作使账户的长哈希转换为短哈希,当--old-passwords有效时不能将它改回长哈希。
5.7.9.1. 更改应用程序密码哈希值的含义
升级到MySQL4.1或更新版本后,使用PASSWORD()为自己的目的生成密码的应用程序会出现兼容性问题。应用程序实际不应这样做,因为PASSWORD()只应用来管理MySQL账户的密码。但一些应用程序使用PASSWORD()用于自己的目的。
如果你从MySQL 4.1之前的版本升级到4.1或以后版本,并在生成长密码哈希值的条件下运行服务器,应用程序使用PASSWORD()破解自己的密码。这种情况下推荐的方法是修改应用程序,使用其它函数,例如SHA1()或MD5(),来产生哈希值。如果不行,你可以使用OLD_PASSWORD()函数,该函数用来提供旧格式的短哈希。但是,请注意OLD_PASSWORD()可能有一天不再被支持。
如果服务器运行在生成短哈希的条件下,可以使用 OLD_PASSWORD()但与PASSWORD()等同。
将MySQL数据库从4.0或更低版本移植到4.1或更高版本的PHP编程人员应参阅旧客户端。