MYSQL 8.0之安全篇
欢迎关注笔者的微信公众号
本文是笔者基于MYSQL8.0官方手册并结合自身实际经验编写,不足之处欢迎指出。编写不易,欢迎署名转载。
密码认证组件
MYSQL是一个组件式架构,MYSQL 8.0+默认启用了密码校验组件validate_password
,它强迫你设置很复杂的密码以保证数据库安全,对于个人来说,你不能设置如123456
这样的密码,当你在设置这样的密码的时候会报如下错误:
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
笔者认为这对于个人学习而言非常碍事,解决方案有两种:
-
修改密码认证策略
-
卸载密码认证组件
修改密码认证策略
-
查看当前认证策略
mysql> SHOW VARIABLES LIKE 'validate_password.%';
这几个认证策略就不一一解释了,就是字面意思。
-
修改认证策略
-
设置密码长度为4个字符
mysql> SET GLOBAL validate_password_length = 4;
-
设置认证策略低级
SET GLOBAL validate_password_policy=LOW;
其他的参数照着改就行,最后最好重启一下服务器。
-
卸载密码认证组件
笔者觉得MYSQL 8.0+默认的密码认证策略非常碍事,所以直接卸载掉了;
-
卸载密码认证组件
UNINSTALL COMPONENT 'file://component_validate_password';
-
安装密码认证组件
INSTALL COMPONENT 'file://component_validate_password';
密码加密方式
问题
在MYSQL 8.0中包含了一下三种加密插件
-
caching_sha2_password
-
sha256_password
-
mysql_native_password
与mysql_native_password
插件相比,caching_sha2_password
和sha256_password
身份验证插件提供了更安全的密码加密。且caching_sha2_password
在性能上优于sha256_password
,因此在MYSQL 8.0中caching_sha2_password
成为了默认的加密插件。
-
修改MYSQL配置文件:
/etc/my.cnf
vim /etc/my.cnf ... [mysqld] default_authentication_plugin=mysql_native_password ...
-
也可以在服务器启动时添加命令行参数
--default-authentication-plugin=mysql_native_password 加上 --initialize选项 或者 --initialize-insecure选项
这种方法是为了兼容那些旧版本客户端能连接到新版本的MYSQL服务器,最好的做法是不要更改服务端参数,而是升级客户端,对于Java程序员来说,升级MYSQL驱动到8.0.9或更高的版本。
修改现有账户的加密方式
在MYSQL安装的时候默认创建了'root'@'localhost'
账号,因此只要修改了账户的加密方式旧版的客户端就可以通过此账号密码连接数据库。
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
访问控制和账户管理
用户名和密码
默认情况下,MYSQL将用户名和连接主机信息存储在user
表,位于mysql
系统数据库中。
注意
在MYSQL中,用户名相同,连接主机不同被认为是两个账号。
%
表示这个账号可从任何IP地址连接到服务器端。
默认情况下MYSQL只创建了'root'@'localhost'
这个账号,这只能从本地连接到数据库,如果要远程连接需要创建新的用户,连接主机IP设为%
,笔者这里创建了repl
和root
两个可从远程连接的账户。
下面是MYSQL关于用户名和账户的一些注意事项:
-
MYSQL的用户名最大长度限制为32字符长度,这是一种硬编码,无法更改。
-
MYSQL的账户密码通过加密插件加密后存储在
user
表中。 -
如果用户名和密码使用了非ASCII字符,那么可以在连接时通过
MYSQL_SET_CHARSET_NAME
选项指定。 -
MYSQL不支持
ucs2
,utf16
,utf32
,这三种字符集。 -
如果在命令行中通过
--password
或-p
选项指定密码,那么密码与选项之间不允许有空白。$ mysql -u finley -p db_name
以上
db_name
会认为是连接的数据库名而不是密码。$ mysql -u finley -ppassword db_name
MYSQL特权
官方文档](https://dev.mysql.com/doc/refman/8.0/en/privileges-provided.html)
MYSQL中的特权是MYSQL用来判断账户是否具有能执行某种操作的凭证。
MYSQL中特权分三类:
-
管理员特权,这是全局特权,可以操作整个服务器资源,
root
用户就是管理员特权,可以操作所有资源 -
数据库特权,适用于数据库及其中的所有对象。可以为特定数据库或全局授予这些特权,以便将它们应用于所有数据库。
-
可以为数据库中的特定对象,数据库中给定类型的所有对象(例如数据库中的所有表)或所有对象的全局对象授予表,索引,视图和存储例程等数据库对象的特权。所有数据库中给定类型的对象。
特权分类
MYSQL特权分为静态特权和动态特权,特权是静态的(内置于服务器中)还是动态的(在运行时定义)对MYSQL行为有不同的影响。特权是静态还是动态会影响将其授予用户帐户和角色的可用性。
MYSQL的特权信息存储在mysql
数据库中,具体的数据表下一章会介绍。
静态特权
特权 | 表中字段 | 上下文 |
---|---|---|
ALL PRIVILEGES |
Synonym for “all privileges” | Server administration |
ALTER |
Alter_priv |
Tables |
ALTER ROUTINE |
Alter_routine_priv |
Stored routines |
CREATE |
Create_priv |
Databases, tables, or indexes |
CREATE ROLE |
Create_role_priv |
Server administration |
CREATE ROUTINE |
Create_routine_priv |
Stored routines |
CREATE TABLESPACE |
Create_tablespace_priv |
Server administration |
CREATE TEMPORARY TABLES |
Create_tmp_table_priv |
Tables |
CREATE USER |
Create_user_priv |
Server administration |
CREATE VIEW |
Create_view_priv |
Views |
DELETE |
Delete_priv |
Tables |
DROP |
Drop_priv |
Databases, tables, or views |
DROP ROLE |
Drop_role_priv |
Server administration |
EVENT |
Event_priv |
Databases |
EXECUTE |
Execute_priv |
Stored routines |
FILE |
File_priv |
File access on server host |
GRANT OPTION |
Grant_priv |
Databases, tables, or stored routines |
INDEX |
Index_priv |
Tables |
INSERT |
Insert_priv |
Tables or columns |
LOCK TABLES |
Lock_tables_priv |
Databases |
PROCESS |
Process_priv |
Server administration |
PROXY |
See proxies_priv table |
Server administration |
REFERENCES |
References_priv |
Databases or tables |
RELOAD |
Reload_priv |
Server administration |
REPLICATION CLIENT |
Repl_client_priv |
Server administration |
REPLICATION SLAVE |
Repl_slave_priv |
Server administration |
SELECT |
Select_priv |
Tables or columns |
SHOW DATABASES |
Show_db_priv |
Server administration |
SHOW VIEW |
Show_view_priv |
Views |
SHUTDOWN |
Shutdown_priv |
Server administration |
SUPER |
Super_priv |
Server administration |
TRIGGER |
Trigger_priv |
Tables |
UPDATE |
Update_priv |
Tables or columns |
USAGE |
Synonym for "no privileges" |
Server administration |
动态特权
特权 | 上下文 |
---|---|
APPLICATION_PASSWORD_ADMIN |
Dual password administration |
AUDIT_ADMIN |
Audit log administration |
BACKUP_ADMIN |
Backup administration |
BINLOG_ADMIN |
Backup and Replication administration |
BINLOG_ENCRYPTION_ADMIN |
Backup and Replication administration |
CLONE_ADMIN |
Clone administration |
CONNECTION_ADMIN |
Server administration |
ENCRYPTION_KEY_ADMIN |
Server administration |
FIREWALL_ADMIN |
Firewall administration |
FIREWALL_USER |
Firewall administration |
GROUP_REPLICATION_ADMIN |
Replication administration |
INNODB_REDO_LOG_ARCHIVE |
Redo log archiving administration |
NDB_STORED_USER |
NDB Cluster |
PERSIST_RO_VARIABLES_ADMIN |
Server administration |
REPLICATION_APPLIER |
PRIVILEGE_CHECKS_USER for a replication channel |
REPLICATION_SLAVE_ADMIN |
Replication administration |
RESOURCE_GROUP_ADMIN |
Resource group administration |
RESOURCE_GROUP_USER |
Resource group administration |
ROLE_ADMIN |
Server administration |
SESSION_VARIABLES_ADMIN |
Server administration |
SET_USER_ID |
Server administration |
SHOW_ROUTINE |
Server administration |
SYSTEM_USER |
Server administration |
SYSTEM_VARIABLES_ADMIN |
Server administration |
TABLE_ENCRYPTION_ADMIN |
Server administration |
VERSION_TOKEN_ADMIN |
Server administration |
XA_RECOVER_ADMIN |
Server administration |
有关特权的具体解释请参看官方文档,这里就不一一解释了,大部分特权正常情况下都不会用到的,用到时再参看官方解释。
授权表
mysql
系统数据库中包含了几个授权表,这些表包含有关用户帐户及其所拥有特权的信息。
注意
坚决禁止使用INSERT
,UPDATE
,DELETE
语句直接修改这些表,这会有极大地风险,推荐使用CREATE USER
,GRANT
,REVOKE
语句操作权限。
授权表预览
以下授权表全部位于mysql
数据库中
-
user
: 用户帐户,静态全局特权和其他非特权字段。 -
global_grants
: 动态全局特权。 -
db
: 数据库级特权。 -
tables_priv
: 表级特权。 -
columns_priv
: 字段级特权。 -
procs_priv
: 存储过程和函数特权。 -
proxies_priv
: 代理用户权限。 -
default_roles
: 默认用户角色。 -
role_edges
: Edges for role subgraphs. -
password_history
: 密码更改历史记录。
在MySQL 8.0中,授权表使用InnoDB
存储引擎并且是事务性的。在MySQL 8.0之前,授权表使用MyISAM
存储引擎,并且是非事务性的。授予表存储引擎的这一更改使帐户管理语句(例如CREATE USER或GRANT)的行为也可以随之更改。以前,命名多个用户的帐户管理语句可能对某些用户成功而对其他用户失败。现在,每个语句都是事务性的,并且对于所有指定的用户都成功,或者回滚,如果发生任何错误,则无效。
每个授权表都包含作用域列和特权列:
- 范围列确定表中每一行的范围;也就是说,该行适用的上下文。例如,具有“主机”和“用户”值分别为“ h1.example.net”和“ bob”的用户表行适用于验证指定了bob用户名的客户端从主机h1.example.net与服务器建立的连接。同样,当bob从主机h1.example.net连接以访问报告数据库时,将应用具有Host,User和Db列值分别为’h1.example.net’,'bob’和’reports’的db表行。table_priv和columns_priv表包含范围列,这些范围列指示每行适用的表或表/列组合。procs_priv作用域列指示每行适用的存储例程。
- 特权列指示表行授予哪些特权;也就是说,它允许执行哪些操作。服务器将各种授权表中的信息组合在一起,以形成用户权限的完整描述。
user
和 db
授权表
Table Name | user |
db |
---|---|---|
Scope columns | Host |
Host |
User |
Db |
|
User |
||
特权字段 | 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 |
||
安全字段 | 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 |
||
资源控制字段 | max_questions |
|
max_updates |
||
max_connections |
||
max_user_connections |
访问控制
阶段一:连接验证
当客户端尝试连接到MySQL服务器时,服务器根据以下条件接受或拒绝连接:
- 提供的的身份凭据以及是否可以通过提供正确的密码来验证身份
- 账户是否已被锁定
服务器首先检查凭据,然后检查帐户锁定状态。任一步骤失败都会导致服务器完全拒绝您的访问。否则,服务器将接受连接,然后进入阶段二并等待请求。
凭据校验主要是通过校验user
表中的user
,host
,authentication_string
三个字段,账户锁定状态存储在user
表中account_locked
字段中。
使用三个用户表作用域列(主机,用户和认证字符串)执行凭据检查。锁定状态记录在用户表account_locked列中。仅当某些用户表行中的“主机”和“用户”列与客户端主机名和用户名匹配,客户端提供该行中指定的密码且account_locked值为“ N”时,服务器才接受连接。仅当某些用户表行中的user
和host
字段与客户端主机名和用户名匹配,客户端提供该行中指定的密码且account_locked
值为N
时,服务器才接受连接。
客户端身份基于以下两个信息:
- 客户端主机,即域名或IP地址
- 用户名,例如:
root
如果user
列的值是非空白的,则传入连接中的用户名必须完全匹配。如果user
值为空白,则它与任何用户名匹配。如果与传入连接匹配的用户表行的用户名为空,则该用户将被视为没有名称的匿名用户,而不是具有客户端实际指定的名称的用户。这意味着在连接持续时间内(即在第2阶段),将使用空白用户名进行所有进一步的访问检查。
user
表中的非空白authentication_string
值表示加密的密码。MySQL不会将密码存储为明文,任何人都可以看到。而是将尝试连接的用户提供的密码进行加密(使用由帐户身份验证插件实现的密码哈希方法)。然后在检查密码是否正确时在连接过程中使用加密的密码。这样就不会在连接上传输加密密码。
mysql> select user, authentication_string from user where user = 'root' and host = '%';
+------+--------------------------------------------------------------------------------------------- ---------+
| user | authentication_string |
+------+-------------------------------------------------------------------------------------------------------+
| root | $A$005$%V,~wy({Nt{pt)1k?I,g6lMrrsej3UC5taF4V6/clkpGaYogHf7FIzwgijo3h9 |
+------+-------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
警告
从MySQL的角度来看,加密密码是真实密码,因此您绝不应该允许任何人访问它。特别是,请勿授予非管理用户对
mysql
系统数据库中表的读取权限。
下表显示了user
表中user
和host
的各种组合如何应用于传入连接:
user |
host |
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.* 域名连接 |
'fred' |
'198.51.100.177' |
fred 只能从IP地址 198.51.100.177 连接服务器 |
'fred' |
'198.51.100.%' |
fred 可以从198.51.100.* 网段连接 |
'fred' |
'198.51.100.0/255.255.255.0' |
等同于上一行 |
当客户端传入的身份凭据匹配多个条目时服务器必须确定要使用哪个匹配项。它可以按以下方式解决此问题:
- 每当服务器将用户表读入内存时,它都会对行进行排序。
- 客户端尝试连接时,服务器将按排序顺序浏览各行。
- 服务器使用与客户端主机名和用户名匹配的第一行。
服务器使用排序规则,该规则首先对具有最特定的主机值的行进行排序。主机名和IP地址是最具体的。(原义IP地址的特异性不受其是否具有网络掩码的影响,因此198.51.100.13
和198.51.100.0/255.255.255.0
被认为是同等具体的。)模式'%'
表示“任何主机”,并且最少具体。空字符串""也表示“任何主机”,但在%
之后排序。具有相同“主机”值的行将按照最特定的user
值进行排序(空白的user
值表示“任何用户”,并且是最不特定的)。对于具有相同特定的user
和host
值的行,顺序是不确定的。
要查看其工作原理,假设user
表如下所示:
+---------------+----------+-
| Host | User | ...
+---------------+----------+-
| % | root | ...
| % | jeffrey | ...
| localhost | root | ...
| localhost | | ...
+------------ --+----------+-
当服务器将表读入内存时,它将使用刚刚描述的规则对行进行排序。排序后的结果如下所示:
当客户端尝试连接时,服务器将浏览已排序的行并使用找到的第一个匹配项。对于jeffrey
与localhost
的连接,表中的两行匹配:host
和user
值为'localhost'
和''
的那一行,以及host
和user
值为'%'
和'jeffrey'
的那一行。localhost
行首先以排序顺序出现,因此服务器会使用这行。
这是另一个例子。假设用户表如下所示:
排序后的结果如下:
第一行匹配来自h1.example.net
的jeffrey
连接,第二行匹配来自任何主机的jeffrey
连接。
提示
常见的误解是认为,当客户端从某个主机以用户名密码登录时服务器会完全匹配其信息,这是不对的。前面的示例对此进行了说明,其中
jeffrey
与h1.example.net
的连接首先不匹配包含jeffrey
作为user
字段值的行,而是不匹配用户名的行。结果,即使jeffrey
在连接时指定了用户名,他仍被认证为匿名用户。
如果您能够连接到服务器,但是特权不是您所期望的,则您可能已通过其他帐户的身份验证。要找出服务器用来验证您身份的帐户,请使用CURRENT_USER()
函数。此函数会返回aaa@qq.com_name
格式返回一个值,该值指示匹配的用户表行中的user
和host
值。
阶段二:请求验证
建立连接后,服务器进入访问控制的第二阶段。对于通过该连接发出的每个请求,服务器将确定要执行的操作,然后检查是否具有足够的特权。这是授权表中的特权列起作用的地方。这些特权可以来自任何用户,global_grants
,db
,tables_priv
,columns_priv
或procs_priv
表。
user
和global_grants
表授予全局特权。这些表中给定帐户的行表示无论默认数据库是什么,在全局基础上应用的帐户特权。例如,如果用户表授予您DELETE
特权,则可以从服务器主机上任何数据库中的任何表中删除行。明智的做法是仅将需要特权的用户授予用户表中的特权,例如数据库管理员。对于其他用户,请将用户表中的所有特权都设置为N
,并仅在更特定的级别(对于特定的数据库,表,列或例程)授予特权。也可以全局授予数据库特权,但可以撤销部分权限来限制它们在特定数据库上的执行。db
表授予特定于数据库的特权。该表的scope
列中的值可以采用以下形式:
- 空的用户值与匿名用户匹配。非空值从字面上匹配;用户名中没有通配符。
- 通配符
%
和可以在host
和db
列中使用。这些具有与使用LIKE
运算符执行的模式匹配操作相同的含义。如果要在授予特权时按字面使用任何一个字符,则必须使用反斜杠将其转义。例如,要将下划线字符(_)包括在数据库名称中,请在GRANT
语句中将其指定为\_
。 -
%
或空白host
值表示“任何主机”。 -
%
或空白db
值表示“任何数据库”。
服务器将数据库表读入内存,并在读取user
表的同时对其进行排序。服务器根据host
,db
和user
列对数据库表进行排序。与用户表一样,排序将最具体的值放在最前面,最不具体的值放在最后,当服务器寻找匹配的行时,它将使用找到的第一个匹配项。
服务器使用排序的表来验证它收到的每个请求。对于需要管理特权(例如SHUTDOWN
或RELOAD
)的请求,服务器仅检查user
和global_privilege
表,因为它们是唯一指定管理特权的表。如果这些表中帐户的行允许请求的操作,则服务器将授予访问权限,否则拒绝访问。例如,如果您想执行mysqladmin shutdown
,但是您的用户表行未授予您SHUTDOWN
特权,则服务器将拒绝访问,甚至不检查db
表。(后一个表不包含Shutdown_priv
列,因此无需检查它。)
对于与数据库相关的请求(INSERT
,UPDATE
等),服务器首先在user
表行中检查用户的全局特权(减去部分撤销所施加的任何特权限制)。如果该行允许请求的操作,则授予访问权限。如果用户表中的全局特权不足,则服务器从数据库表中确定用户的数据库特定特权:
服务器在db
表中查找host
,db
和user
列上的匹配项。host
和user
列与连接用户的主机名和MySQL用户名匹配。db
列与用户要访问的数据库匹配。如果主机和用户没有行,则拒绝访问。
在确定db
表行授予的特定于数据库的特权之后,服务器会将它们添加到user
表授予的全局特权中。如果结果允许请求的操作,则授予访问权限。否则,服务器会先检查tables_priv和columns_priv
表中用户的表特权和列特权,将这些特权添加到用户特权中,然后根据结果允许或拒绝访问。对于存储过程操作,服务器使用procs_priv
表而不是table_priv
和columns_priv
。
前面关于如何计算用户特权的描述可以总结如下:
global privileges
OR (database privileges AND host privileges)
OR table privileges
OR column privileges
OR routine privileges
上面介绍的请求权限验证流程可能令人迷惑,如果最初发现全局特权不足以执行请求的操作,则服务器随后会将这些特权添加到数据库,表和列特权中。原因是请求可能需要一种以上的特权。例如,如果执行INSERT INTO ... SELECT
语句,则需要INSERT
和SELECT
特权。您的特权可能是这样的:user
表行授予一个全局特权,而数据库表行授予另一个特权,专门针对相关数据库。在这种情况下,您具有执行请求所必需的特权,但是服务器无法仅从全局特权或数据库特权中分辨出这一点。它必须根据组合的权限做出访问控制决定。
账户管理
创建账户并授权
CREATE USER 'finley'@'localhost'
IDENTIFIED BY 'password';
GRANT ALL
ON *.*
TO 'finley'@'localhost'
WITH GRANT OPTION;
CREATE USER 'finley'@'%.example.com'
IDENTIFIED BY 'password';
GRANT ALL
ON *.*
TO 'finley'@'%.example.com'
WITH GRANT OPTION;
CREATE USER 'admin'@'localhost'
IDENTIFIED BY 'password';
GRANT RELOAD,PROCESS
ON *.*
TO 'admin'@'localhost';
CREATE USER 'dummy'@'localhost';
以上四组例子创建了四个账户,他们分别具有如下属性:
-
前两个帐户的用户名均为finley。两者都是具有执行任何操作的完整全局特权的超级用户帐户。仅当从本地主机连接时,才能使用
'finley' @'localhost'
帐户。'finley'@'%.example.com'
帐户在主机部分使用%
通配符,因此可用于从example.com
域名下的任何主机进行连接。如果有本地主机的匿名用户帐户,则必须使用
'finley'@'localhost'
帐户。如果没有'finley'@'localhost'
帐户,则当finley从本地主机进行连接并且finley
被视为匿名用户时,该匿名用户帐户将具有优先权。原因是匿名用户帐户的主机列值比'finley'@'%'
帐户更具体,因此在用户表排序顺序中排在更早的位置。 -
'admin' @'localhost'
帐户只能由admin
用来从本地主机进行连接。它被授予全局RELOAD
和PROCESS
管理特权。这些特权使admin
用户可以执行mysqladmin reload
,mysqladmin refresh
和mysqladmin flush-xxx
命令以及mysqladmin processlist
。不授予访问任何数据库的特权。您可以使用GRANT
语句添加此类特权。 -
'dummy'@'localhost'
帐户没有密码(不安全,不建议使用)。该帐户只能用于从本地主机连接。这条语句没有授予任何特权,您假定您将使用GRANT
语句授予该帐户特定的特权。
以上的授权语句中的权限名可在之前的MYSQL特权找到。
前面的示例在全局级别授予特权。下一个示例创建三个帐户,并授予他们较低级别的访问权限;即针对特定数据库或数据库中的对象。每个帐户都有一个自定义用户名,但是主机名部分有所不同:
CREATE USER 'custom'@'localhost'
IDENTIFIED BY 'password';
GRANT ALL
ON bankaccount.*
TO 'custom'@'localhost';
CREATE USER 'custom'@'host47.example.com'
IDENTIFIED BY 'password';
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP
ON expenses.*
TO 'custom'@'host47.example.com';
CREATE USER 'custom'@'%.example.com'
IDENTIFIED BY 'password';
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP
ON customer.addresses
TO 'custom'@'%.example.com';
这三个帐户适合在以下情况使用:
-
'custom'@'localhost'
帐户具有访问bankaccount
数据库的所有数据库级特权。该帐户只能用于从本地主机连接到服务器。 -
'custom'@'host47.example.com'
帐户具有访问expenses
数据库的特定数据库级别特权。该帐户只能用于从主机host47.example.com
连接到服务器。 -
'custom'@'%.example.com'
帐户具有特定的表级特权,可以从example.com
域名下的任何主机访问客户数据库中的地址表。由于在帐户名称的主机部分中使用%
通配符,因此该帐户可用于从此域名中的所有计算机连接到服务器。
检查帐户特权和属性
使用SHOW GRANTS
查看帐户的特权:
mysql> SHOW GRANTS FOR 'admin'@'localhost';
+----------------------------------------------------------------------------+
| Grants for aaa@qq.com |
+----------------------------------------------------------------------------+
| GRANT RELOAD, PROCESS ON *.* TO 'admin'@'localhost' |
+----------------------------------------------------------------------------+
使用SHOW CREATE USER
查看帐户的非特权属性:
mysql> SET print_identified_with_as_hex = ON; ''' 账户信息以十六进制打印,保护隐私
mysql> SHOW CREATE USER 'admin'@'localhost'\G
*************************** 1. row ***************************
CREATE USER for aaa@qq.com: CREATE USER 'admin'@'localhost'
IDENTIFIED WITH 'caching_sha2_password'
AS 0x24412430303524301D0E17054E2241362B1419313C3E44326F294133734B30792F436E77764270373039612E32445250786D43594F45354532324B6169794F47457852796E32
REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK
PASSWORD HISTORY DEFAULT
PASSWORD REUSE INTERVAL DEFAULT
PASSWORD REQUIRE CURRENT DEFAULT
启用print_identified_with_as_hex系统变量(自MySQL 8.0.17起可用)会导致SHOW CREATE USER以十六进制字符串而非常规字符串文字形式显示包含不可打印字符的哈希值。
撤销帐户特权
要撤消帐户特权,请使用REVOKE
语句。可以在不同级别撤销特权,就像可以在不同级别授予特权一样。
撤销全局特权:
REVOKE ALL
ON *.*
FROM 'finley'@'%.example.com';
REVOKE RELOAD
ON *.*
FROM 'admin'@'localhost';
撤消数据库级特权:
REVOKE CREATE,DROP
ON expenses.*
FROM 'custom'@'host47.example.com';
撤消表级特权:
REVOKE INSERT,UPDATE,DELETE
ON customer.addresses
FROM 'custom'@'%.example.com';
要检查特权撤销的效果,请使用SHOW GRANTS
:
mysql> SHOW GRANTS FOR 'admin'@'localhost';
+----------------------------------------------------------------+
| Grants for aaa@qq.com |
+----------------------------------------------------------------+
| GRANT PROCESS ON *.* TO 'admin'@'localhost' |
+----------------------------------------------------------------+
删除帐户
要删除帐户,请使用DROP USER
语句。例如,删除一些先前创建的帐户:
DROP USER 'finley'@'localhost';
DROP USER 'finley'@'%.example.com';
DROP USER 'admin'@'localhost';
DROP USER 'dummy'@'localhost';
保留账户
在数据目录初始化期间,MySQL创建应被视为保留的用户帐户。
当刚安装完MYSQL后,会创建以下用户:
-
'root'@'localhost
:用于管理目的。该帐户具有所有特权,是系统帐户,并且可以执行任何操作。严格来说,此帐户名不是保留的,在某种意义上说,某些安装会将根帐户重命名为其他名称,以避免暴露具有众所周知名称的高特权帐户。
-
'mysql.sys'@'localhost'
:用作sys
模式对象的DEFINER
。使用mysql.sys
帐户可避免DBA
重命名或删除根帐户时发生的问题。此帐户已锁定,因此不能用于客户端连接。 -
'mysql.session'@'localhost'
:插件在内部用于访问服务器。此帐户已锁定,因此不能用于客户端连接。该帐户是系统帐户。 -
'mysql.infoschema'@'localhost'
:用作INFORMATION_SCHEMA
视图的DEFINER
。mysql.infoschema
帐户的使用避免了DBA重命名或删除根帐户时发生的问题。此帐户已锁定,因此不能用于客户端连接。
分配账户密码
MySQL将凭据存储在mysql
系统数据库的user
表中。分配或修改密码的操作仅允许具有CREATE USER
特权或mysql
数据库特权(创建新帐户的INSERT
特权,修改现有帐户的UPDATE
特权)的用户使用。如果启用了read_only
系统变量,则使用帐户修改语句(例如CREATE USER
或ALTER USER
)还需要CONNECTION_ADMIN
特权(或不建议使用的SUPER
特权)。
此处的讨论仅汇总了最常见的密码分配语句的语法。
创建用户
要在创建新帐户时分配密码,请使用CREATE USER
并包含IDENTIFIED BY
子句:
CREATE USER 'jeffrey'@'localhost' IDENTIFIED BY 'password';
CREATE USER
还支持用于指定帐户身份验证插件的语法。具体参看官方文档。
修改密码
要为现有帐户分配或更改密码,请使用带有IDENTIFIED BY
子句的ALTER USER
语句:
ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY 'password';
如果不是以匿名用户身份连接的,则可以更改自己的密码,而无需直接指定自己的帐户:
ALTER USER USER() IDENTIFIED BY 'password';
要从命令行更改帐户密码,请使用mysqladmin
命令:
mysqladmin -u user_name -h host_name password "password"
此命令设置密码的帐户是mysql.user
系统表中带有一行的帐户,该行与user
字段中的user_name和host
列中连接的客户端主机相匹配。
警告使用
mysqladmin
设置密码应该被认为是不安全的。在某些系统上,密码对系统状态程序(例如ps
命令)可见,其他用户可能会调用该密码来显示命令行。MySQL客户端通常在初始化序列期间用零覆盖命令行密码参数。但是,仍然有一个短暂的时间间隔,在该时间间隔内该值是可见的。同样,在某些系统上,此覆盖策略无效,并且ps
仍然可以看到密码。(SystemV Unix
系统以及其他系统可能会遇到此问题。)
密码管理
MySQL支持以下密码管理功能:
- 密码到期,要求定期更改密码。
- 密码重用限制,以防止再次选择旧密码。
- 密码验证,要求更改密码还指定要替换的当前密码。
- 双密码,使客户端可以使用主密码或辅助密码进行连接。
- 密码强度评估,要求使用强密码。
- 随机密码生成,作为要求管理员指定明确的文字密码的替代方法。
- 密码失败跟踪,用于在连续多次错误密码登录失败后启用临时帐户锁定。
以下各节描述了这些功能,但密码强度评估功能除外,后者是使用validate_password
组件实现的,在这之前笔者已将这个组件卸载了。
重要MySQL使用mysql系统数据库中的表实现密码管理功能。如果从早期版本升级MySQL,则系统表可能不是最新的。在这种情况下,服务器会在启动过程中将类似于以下消息的消息写入错误日志(确切的数目可能有所不同):
[ERROR] Column count of mysql.user is wrong. Expected 49, found 47. The table is probably corrupted [Warning] ACL table mysql.password_history missing. Some operations may fail.
要解决此问题,请执行MySQL升级过程。请参见升级MySQL。在此之前,无法更改密码。
内部与外部凭证存储
一些身份验证插件在mysql.user
系统表中将帐户凭据存储在MySQL内部:
mysql_native_password
caching_sha2_password
sha256_password
本节中的大多数讨论都适用于此类身份验证插件,因为此处描述的大多数密码管理功能都是基于MySQL本身处理的内部凭据存储。其他身份验证插件将帐户凭据存储在MySQL的外部。对于使用插件针对外部凭据系统执行身份验证的帐户,密码管理也必须在外部针对该系统进行处理。
唯一的例外是登录失败跟踪和临时帐户锁定的选项适用于所有帐户,而不仅限于使用内部凭据存储的帐户,因为MySQL能够评估任何帐户的登录尝试状态,无论它是使用内部帐户还是内部帐户。外部凭证存储。
密码过期策略
MySQL使数据库管理员可以手动使帐户密码过期,并建立自动密码过期策略。可以在全局范围内建立到期策略,并且可以将各个帐户设置为遵从全局策略,或者以特定的每个帐户行为覆盖全局策略。
要手动使帐户密码失效,请使用ALTER USER
语句:
ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE;
该操作在mysql.user
系统表的相应行中标记密码已过期。
根据策略,密码有效期是自动的,并且基于密码有效期,对于给定帐户,将从最近一次密码更改的日期和时间开始对其进行评估。mysql.user
系统表为每个帐户指示上次更改密码的时间,如果服务器的使用期限大于允许的使用期限,则服务器会在客户端连接时自动将其视为过期密码。此方法无需明确的手动密码有效期。
要全局建立自动密码过期策略,请使用default_password_lifetime
系统变量。其默认值为0,这将禁用自动密码过期。如果default_password_lifetime
的值为正整数N
,则表示允许的密码生存期,因此必须每N
天更改一次密码。
示例:
-
要建立密码有效期约为六个月的全局策略,请在服务器
my.cnf
文件中的以下行启动服务器:[mysqld] default_password_lifetime=180
-
要建立密码永不过期的全局策略,请将default_password_lifetime设置为0:
[mysqld] default_password_lifetime=0
-
还可以在运行时设置
default_password_lifetime
并将其持久化:SET PERSIST default_password_lifetime = 180; SET PERSIST default_password_lifetime = 0;
SET PERSIST
设置正在运行的MySQL
实例的值。它还保存该值以继续进行后续服务器重新启动。要更改正在运行的MySQL实例的值而不使它继续进行后续的重新启动,请使用GLOBAL
关键字而不是PERSIST
。
全局密码过期策略适用于尚未设置为覆盖该策略的所有帐户。要为单个帐户建立策略,请使用CREATE USER
和ALTER USER
语句的PASSWORD EXPIRE
选项。
一个对账单示例
-
要求每90天更改一次密码:
CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY; ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
该到期选项将覆盖该语句命名的所有帐户的全局策略。
-
禁用密码有效期:
CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE NEVER; ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE NEVER;
该到期选项将覆盖该语句命名的所有帐户的全局策略。
-
对语句命名的所有帐户遵循全局到期策略:
CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE DEFAULT; ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE DEFAULT;
客户端成功连接后,服务器将确定帐户密码是否已过期:
- 服务器检查密码是否已手动过期。
- 否则,服务器会根据自动密码过期策略检查密码使用期限是否大于其允许的使用期限。如果是这样,则服务器认为密码已过期。
如果密码已过期(无论是手动还是自动),则服务器将断开客户端连接或限制其允许的操作。受限制的客户端执行的操作会导致错误,直到用户建立新的帐户密码为止:
mysql> SELECT 1;
ERROR 1820 (HY000): You must reset your password using ALTER USER
statement before executing this statement.
mysql> ALTER USER USER() IDENTIFIED BY 'password';
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
客户端重置密码后,服务器将恢复会话以及使用该帐户的后续连接的正常访问。管理员用户也可以重设帐户密码,但是该帐户的所有现有受限会话仍然受到限制。使用该帐户的客户端必须断开连接并重新连接,然后语句才能成功执行。
提示
注意尽管可以通过将过期的密码设置为当前值来“重置”它,但出于良好的政策考虑,最好选择其他密码。DBA可以通过建立适当的密码重用策略来强制不重用。请参阅密码重用策略。
密码重用策略
MySQL允许对重用以前的密码进行限制。可以根据密码更改的次数,经过的时间或同时基于两者来建立重用限制。可以在全局范围内建立重用策略,并且可以将各个帐户设置为遵从全局策略,或者使用特定的按帐户行为替换全局策略。
帐户的密码历史记录由过去分配的密码组成。MySQL可以限制从以下历史记录中选择新密码:
-
如果根据密码更改次数限制了帐户,则无法从指定数量的最新密码中选择新密码。例如,如果密码更改的最小次数设置为3,则新密码不能与最新的3个密码相同。
-
如果根据时间限制了帐户,则无法从历史记录中指定天数以上的密码中选择新密码。例如,如果密码重用间隔设置为60,则新密码一定不能属于过去60天内先前选择的密码。
提示
空密码不计入密码历史记录,并且随时可以重复使用。
要全局建立密码重用策略,请使用password_history
和password_reuse_interval
系统变量。
示例
-
要禁止重复使用最后6个密码或比365天新的密码,请在服务器my.cnf文件中添加以下行:
[mysqld] password_history=6 password_reuse_interval=365
-
要在运行时设置和保留变量,请使用如下语句:
SET PERSIST password_history = 6; SET PERSIST password_reuse_interval = 365;
SET PERSIST
设置正在运行的MySQL实例的值。它还保存该值以继续进行后续服务器重新启动。要更改正在运行的MySQL实例的值而不使它继续进行后续的重新启动,请使用GLOBAL
关键字而不是PERSIST
。
一个对账单示例
-
在允许重复使用之前,至少需要更改5次密码:
CREATE USER 'jeffrey'@'localhost' PASSWORD HISTORY 5; ALTER USER 'jeffrey'@'localhost' PASSWORD HISTORY 5;
此历史记录长度选项将覆盖该语句命名的所有帐户的全局策略。
-
至少需要经过365天,才能允许重复使用:
CREATE USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL 365 DAY; ALTER USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL 365 DAY;
这个“过去时间”的选项会覆盖该语句命名的所有帐户的全局策略。
-
要结合两种类型的重用限制,请一起使用
PASSWORD HISTORY
和PASSWORD REUSE INTERVAL
:CREATE USER 'jeffrey'@'localhost' PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 365 DAY; ALTER USER 'jeffrey'@'localhost' PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 365 DAY;
这些选项覆盖了该语句命名的所有帐户的全局策略重用限制。
-
对于这两种类型的重用限制,请遵循全局策略:
CREATE USER 'jeffrey'@'localhost' PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT; ALTER USER 'jeffrey'@'localhost' PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT;
密码验证要求策略
从MySQL 8.0.13开始,可能需要通过指定要替换的当前密码来验证更改帐户密码的尝试。这使DBA可以防止用户在不证明他们知道当前密码的情况下更改密码。否则,例如,如果一个用户暂时退出终端会话而没有注销,并且恶意用户使用该会话来更改原始用户的MySQL密码,则可能会发生此类更改。这可能会带来不幸的后果:
- 在管理员重置帐户密码之前,原始用户将无法访问MySQL。
- 在密码重置发生之前,恶意用户可以使用良性用户更改的凭据访问MySQL。
可以在全局范围内建立密码验证策略,并且可以将各个帐户设置为遵从全局策略,或者使用特定的按帐户行为覆盖全局策略。
对于每个帐户,其mysql.user
行指示是否存在特定于帐户的设置,要求对当前密码进行验证以进行密码更改尝试。该设置由CREATE USER
和ALTER USER
语句的PASSWORD REQUIRE
选项建立:
-
如果帐户设置为
PASSWORD REQUIRE CURRENT
,则密码更改必须指定当前密码。 -
如果帐户设置为
PASSWORD REQUIRE CURRENT OPTIONAL
,则可以更改密码,但不必指定当前密码。 -
如果帐户设置为
PASSWORD REQUIRE CURRENT DEFAULT
,则password_require_current
系统变量将确定该帐户的验证所需策略:- 如果启用了
password_require_current
,则密码更改必须指定当前密码。 - 如果禁用
password_require_current
,则可以但不必指定当前密码来更改密码。
- 如果启用了
换句话说,如果帐户设置不是PASSWORD REQUIRE CURRENT DEFAULT
,则帐户设置优先于password_require_current
系统变量建立的全局策略。否则,该帐户将遵循password_require_current
设置。
默认情况下,密码验证是可选的:password_require_current
被禁用,并且没有PASSWORD REQUIRE
选项创建的帐户默认为PASSWORD REQUIRE CURRENT DEFAULT
。
下表显示了每个帐户设置如何与password_require_current
系统变量值进行交互,以确定需要帐户密码验证的策略。
密码验证策略
每个账户的设置 | password_require_current 系统变量 | 修改密码前是否需要确认原密码 |
---|---|---|
PASSWORD REQUIRE CURRENT |
OFF |
Yes |
PASSWORD REQUIRE CURRENT |
ON |
Yes |
PASSWORD REQUIRE CURRENT OPTIONAL |
OFF |
No |
PASSWORD REQUIRE CURRENT OPTIONAL |
ON |
No |
PASSWORD REQUIRE CURRENT DEFAULT |
OFF |
No |
PASSWORD REQUIRE CURRENT DEFAULT |
ON |
Yes |
注意
特权用户可以在不指定当前密码的情况下更改任何帐户密码,而不管验证要求的策略如何。特权用户是具有mysql
系统数据库的全局CREATE USER
特权或UPDATE
特权的用户。
要全局建立密码验证策略,请使用password_require_current
系统变量。其默认值为OFF
,因此不需要更改帐户密码来指定当前密码。
示例:
-
要建立密码更改必须指定当前密码的全局策略,请在服务器
my.cnf
文件中使用以下几行启动服务器:[mysqld] password_require_current=ON
-
要在运行时设置和保留
password_require_current
,请使用以下语句之一:SET PERSIST password_require_current = ON; SET PERSIST password_require_current = OFF;
SET PERSIST设置正在运行的MySQL实例的值。它还保存该值以继续进行后续服务器重新启动。要更改正在运行的MySQL实例的值而不使它继续进行后续的重新启动,请使用
GLOBAL
关键字而不是PERSIST
。
要求全局密码验证的策略适用于尚未设置为覆盖该策略的所有帐户。要为单个帐户建立策略,请使用CREATE USER
和ALTER USER
语句的PASSWORD REQUIRE
选项。
一个对账单示例
-
要求密码更改指定当前密码:
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT; ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT;
该验证选项将覆盖该语句命名的所有帐户的全局策略。
-
不需要更改密码指定当前密码(可以但不必提供当前密码):
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT OPTIONAL; ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT OPTIONAL;
当用户使用
ALTER USER
或SET PASSWORD
语句更改密码时,对当前密码进行验证。这些示例使用ALTER USER
,这比SET PASSWORD
更为可取,但是这里描述的原理对于两个语句都是相同的。 -
对语句命名的所有帐户都遵循全局密码验证所需的策略:
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT; ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT;
当用户使用ALTER USER
或SET PASSWORD
语句更改密码时,对当前密码进行验证。这些示例使用ALTER USER
,这比SET PASSWORD
更为可取,但是这里描述的原理对于两个语句都是相同的。
在更改密码的语句中,REPLACE
子句指定要替换的当前密码。
示例:
-
更改当前用户的密码:
ALTER USER USER() IDENTIFIED BY 'auth_string' REPLACE 'current_auth_string';
-
更改指定用户的密码:
ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY 'auth_string' REPLACE 'current_auth_string';
-
更改命名用户的身份验证插件和密码:
ALTER USER 'jeffrey'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'auth_string' REPLACE 'current_auth_string';
REPLACE
子句的工作方式如下:
-
如果需要更改帐户密码以指定当前密码,则必须给出
REPLACE
,以验证尝试进行更改的用户实际上知道当前密码。 -
如果可以(但不必指定当前密码)更改帐户密码,则
REPLACE
是可选的。 -
如果指定了
REPLACE
,则必须指定正确的当前密码,否则会发生错误。 -
即使
REPLACE
是可选的,也是如此。 -
仅当更改当前用户的帐户密码时才能指定
REPLACE
。(这意味着在刚刚显示的示例中,除非当前用户为jeffrey
,否则显式命名jeffrey
帐户的语句将失败。)即使特权用户尝试对另一个用户进行更改,也是如此。但是,这样的用户可以在不指定REPLACE
的情况下更改任何密码。 -
二进制日志中省略了
REPLACE
,以避免向其写入明文密码。
双密码支持
从MySQL 8.0.14开始,允许用户帐户具有双重密码,分别指定为主要和次要密码。双密码功能使在以下情况下无缝执行凭据更改成为可能:
- 系统具有大量的MySQL服务器,可能涉及复制。
- 多个应用程序连接到不同的MySQL服务器。
- 必须对应用程序用来连接到服务器的一个或多个帐户进行定期的凭据更改。
考虑仅在一个帐户只允许使用一个密码的情况下,在上述类型的方案中必须如何执行凭证更改。在这种情况下,在更改帐户密码并将其传播到所有服务器以及何时更新所有使用该帐户的应用程序以使用新密码的时间上,必须紧密合作。此过程可能涉及服务器或应用程序不可用的停机时间。
使用双重密码,可以更容易地,分阶段地更轻松地进行凭据更改,而无需密切合作,也无需停机:
- 对于每个受影响的帐户,在服务器上建立新的主密码,并保留当前密码作为辅助密码。这使服务器能够识别每个帐户的主密码或辅助密码,而应用程序可以继续使用与以前相同的密码(现在称为辅助密码)连接到服务器。
- 密码更改传播到所有服务器后,修改使用任何受影响帐户的应用程序以使用帐户主密码进行连接。
- 将所有应用程序从辅助密码迁移到主要密码后,不再需要辅助密码,可以将其丢弃。此更改传播到所有服务器后,只能使用每个帐户的主密码进行连接。凭据更改现已完成。
MySQL通过保存和丢弃辅助密码的语法实现了双密码功能:
-
当您分配新的主要密码时,
ALTER USER
和SET PASSWORD
语句的RETAIN CURRENT PASSWORD
子句会将帐户当前密码保存为其次要密码。 -
ALTER USER
的DISCARD OLD PASSWORD
子句将丢弃帐户的辅助密码,仅保留主密码。
假设对于先前描述的凭据更改方案,应用程序使用名为aaa@qq.com host1.example.com
的帐户连接到服务器,并且该帐户的密码将从password_a
更改为password_b
。
要执行此凭据更改,请按以下方式使用ALTER USER
:
-
在不是备份从节点的每个服务器上,将
password_b
设置为新的appuser1
主密码,并保留当前密码作为辅助密码:ALTER USER 'appuser1'@'host1.example.com' IDENTIFIED BY 'password_b' RETAIN CURRENT PASSWORD;
-
等待密码更改,以将整个系统复制到所有从属服务器。
-
修改使用
appuser1
帐户的每个应用程序,以使其使用password_b
而不是password_a
的密码连接到服务器。 -
此时,不再需要辅助密码。在不是复制从服务器的每个服务器上,放弃辅助密码:
ALTER USER 'appuser1'@'host1.example.com' DISCARD OLD PASSWORD;
-
丢弃密码更改复制到所有从属服务器后,凭据更改完成。
RETAIN CURRENT PASSWORD
和DISCARD OLD PASSWORD
子句具有以下效果:
-
RETAIN CURRENT PASSWORD
保留帐户当前密码作为其次要密码,以替换任何现有的次要密码。新密码成为主密码,但是客户端可以使用主密码或辅助密码使用该帐户连接到服务器。(例外:如果ALTER USER
或SET PASSWORD
语句指定的新密码为空,则即使给出了RETAIN CURRENT PASSWORD
,二级密码也将为空。) -
如果为具有空主密码的帐户指定
RETAIN CURRENT PASSWORD
,则该语句将失败。 -
如果帐户具有辅助密码,而您在未指定
RETAIN CURRENT PASSWORD
的情况下更改了其主密码,则辅助密码将保持不变。 -
对于
ALTER USER
,如果您更改分配给该帐户的身份验证插件,则辅助密码将被丢弃。如果您更改身份验证插件,并且还指定了RETAIN CURRENT PASSWORD
,则该语句将失败。 -
对于
ALTER USER
,DISCARD OLD PASSWORD
会丢弃辅助密码(如果存在)。该帐户仅保留其主密码,并且客户端只能使用该主密码来使用该帐户连接到服务器。
修改辅助密码的语句需要以下特权:
-
要对适用于您自己的帐户的
ALTER USER
和SET PASSWORD
语句使用RETAIN CURRENT PASSWORD
或DISCARD OLD PASSWORD
子句,必须具有APPLICATION_PASSWORD_ADMIN
特权。由于大多数用户仅需要一个密码,因此需要特权来操作您自己的辅助密码。 -
如果允许一个帐户操作所有帐户的辅助密码,则应授予该帐户
CREATE USER
特权,而不是APPLICATION_PASSWORD_ADMIN
。
随机密码生成
从MySQL 8.0.18开始,CREATE USER
,ALTER USER
和SET PASSWORD
语句具有为用户帐户生成随机密码的功能,可以替代要求管理员指定的文字密码的替代方法。本节描述了生成随机密码的共同特征。
默认情况下,生成的随机密码的长度为20个字符。该长度由generate_random_password_length系统变量控制,范围为5到255。
对于每个为其语句生成随机密码的帐户,该语句将密码存储在mysql.user
系统表中,该密码已针对帐户身份验证插件进行了适当的哈希处理。该语句还在结果集的一行中返回明文密码,以使其对执行该语句的用户或应用程序可用。结果集列被命名为用户,主机和生成的密码,指示标识mysql.user
系统表中受影响行的用户名和主机名值,以及明文生成的密码。
mysql> CREATE USER
'u1'@'localhost' IDENTIFIED BY RANDOM PASSWORD,
'u2'@'%.example.com' IDENTIFIED BY RANDOM PASSWORD,
'u3'@'%.org' IDENTIFIED BY RANDOM PASSWORD;
+------+---------------+------------------------+
| user | host | generated password |
+------+----------------------+-----------------+
| u1 | localhost | BA;aaa@qq.com+y{&TDFF |
| u2 | %.example.com | YX5>aaa@qq.com>sn9azmD4 |
| u3 | %.org | ;GfD44l,)C}PI/6)4TwZ |
+------+---------------+------------------------+
mysql> ALTER USER
'u1'@'localhost' IDENTIFIED BY RANDOM PASSWORD,
'u2'@'%.example.com' IDENTIFIED BY RANDOM PASSWORD;
+------+---------------+----------------------+
| user | host | generated password |
+------+---------------------+----------------+
| u1 | localhost | yhXBrBp.;Y6abB)e_UWr |
| u2 | %.example.com | >M-vmjp9DTY6}hkp,RcC |
+------+---------------+----------------------+
mysql> SET PASSWORD FOR 'u3'@'%.org' TO RANDOM;
+------+-------+-----------------------+
| user | host | generated password |
+------+-------+-----------------------+
| u3 | %.org | o(._oNn)d;FC<vJIDg9M |
+------+-------+-----------------------+
将CREATE USER
,ALTER USER
或SET PASSWORD
语句生成一个帐户的随机密码,并以IDENTIFIED WITH auth_plugin AS'auth_string'
子句作为CREATE USER
或ALTER USER
语句写入二进制日志中,其中auth_plugin
是帐户身份验证插件,auth_string
是帐户的哈希密码值。
如果安装了validate_password
组件,则它实施的策略对生成的密码无效。(密码验证的目的是帮助人们创建更好的密码。)
登录失败跟踪和临时帐户锁定
从MySQL 8.0.19开始,管理员可以配置用户帐户,以便太多连续登录失败会导致临时帐户锁定。
在这种情况下,“登录失败”表示客户端在连接尝试期间无法提供正确的密码。它不包括由于未知用户或网络问题等原因导致的连接失败。对于具有双重密码的帐户(请参阅双重密码支持),两个帐户密码均视为正确。
可以使用CREATE USER
和ALTER USER
语句的FAILED_LOGIN_ATTEMPTS
和PASSWORD_LOCK_TIME
选项对每个帐户配置所需的登录失败次数和锁定时间。
示例:
CREATE USER 'u1'@'localhost' IDENTIFIED BY 'password'
FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 3;
ALTER USER 'u2'@'localhost'
FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME UNBOUNDED;
当发生太多连续登录失败时,客户端会收到如下错误:
ERROR 3957 (HY000): Access denied for user user.
Account is blocked for D day(s) (R day(s) remaining)
due to N consecutive failed logins.
使用以下选项:
-
FAILED_LOGIN_ATTEMPTS
N
此选项指示是否跟踪指定错误密码的帐户登录尝试。数字*
N
*指定有多少连续的错误密码导致临时帐户锁定。 -
PASSWORD_LOCK_TIME {
N
| UNBOUNDED}此选项指示在连续多次登录尝试后提供密码错误后锁定帐户的时间。该值是数字*
N
*,用于指定帐户保持锁定的天数,或者是UNBOUNDED
,用于指定当帐户进入临时锁定状态时,该状态的持续时间不受限制,并且直到帐户解锁后才结束。解锁的条件将在后面描述。
每个选项的*N
*允许值范围是0到32767。值为0将禁用该选项。
登录失败跟踪和临时帐户锁定具有以下特征:
-
为了使帐户能够进行失败登录跟踪和临时锁定,其
FAILED_LOGIN_ATTEMPTS
和PASSWORD_LOCK_TIME
选项都必须为非零。 -
对于
CREATE USER
,如果未指定FAILED_LOGIN_ATTEMPTS
或PASSWORD_LOCK_TIME
,则对于该语句命名的所有帐户,其隐式默认值为0。这意味着将禁用登录失败跟踪和临时帐户锁定。(这些隐式默认值也适用于引入失败登录跟踪之前创建的帐户。) -
对于
ALTER USER
,如果未指定FAILED_LOGIN_ATTEMPTS
或PASSWORD_LOCK_TIME
,则对于该语句命名的所有帐户,其值均保持不变。 -
为了使临时帐户锁定,密码失败必须是连续的。在达到失败登录的
FAILED_LOGIN_ATTEMPTS
值之前发生的任何成功登录都会导致失败计数重置。例如,如果FAILED_LOGIN_ATTEMPTS
为4,并且发生了三个连续的密码失败,则必须再出现一个失败才能开始锁定。但是,如果下一次登录成功,则将重置该帐户的失败登录计数,以便再次需要四个连续的失败来锁定。 -
一旦开始临时锁定,即使使用了正确的密码,也无法成功登录,直到锁定持续时间过去或通过以下讨论中列出的一种帐户重置方法将帐户解锁。
当服务器读取授权表时,它将初始化每个帐户的状态信息,其中包括是否启用了失败登录跟踪,该帐户当前是否被临时锁定以及是否已开始锁定(如果已启用)以及该帐户发生临时锁定之前的失败次数未锁定。
-
在以下任何一种情况下,都会对所有帐户进行全局重置:
- 服务器重新启动。
- 执行
FLUSH PRIVILEGES
。(使用--skip-grant-tables
启动服务器会导致无法读取授权表,这会禁用失败登录跟踪。在这种情况下,首次执行FLUSH PRIVILEGES
会导致服务器读取授权表并启用失败-登录跟踪,以及重置所有帐户。)
-
在以下任何一种情况下都会发生按帐户重置的情况:
- 成功登录该帐户。
- 锁定持续时间过去了。在这种情况下,登录失败计数将在下次尝试登录时重置。
- 为该帐户执行将
FAILED_LOGIN_ATTEMPTS
或PASSWORD_LOCK_TIME
(或两者)设置为任何值(包括当前选项值)的帐户,或对该帐户执行ALTER USER ... UNLOCK
语句。
该帐户的其他
ALTER USER
语句对其当前的失败登录计数或锁定状态没有影响。
登录失败跟踪与用于检查凭据的登录帐户相关联。如果正在使用用户代理,则会对代理用户(而不是被代理用户)进行跟踪。也就是说,跟踪绑定到USER()
指示的帐户,而不是CURRENT_USER()
指示的帐户。有关代理和代理用户之间区别的信息。
账户锁定
MySQL支持对CREATE USER
和ALTER USER
语句使用ACCOUNT LOCK
和ACCOUNT UNLOCK
子句来锁定和解锁用户帐户:
- 与
CREATE USER
一起使用时,这些子句指定新帐户的初始锁定状态。在没有任何一个子句的情况下,将以解锁状态创建帐户。 - 如果启用
validate_password
组件,则即使该帐户已锁定,也将不允许创建没有密码的帐户。
从MySQL 8.0.19开始,ALTER USER ... UNLOCK
解锁由于登录失败而被临时锁定的语句命名的任何帐户。
帐户锁定状态记录在mysql.user
系统表的account_locked
列中。SHOW CREATE USER
的输出指示帐户是锁定还是未锁定。
如果客户端尝试连接到锁定的帐户,则尝试将失败。服务器递增Locked_connects
状态变量,该变量指示尝试连接到锁定帐户的次数,返回ER_ACCOUNT_HAS_BEEN_LOCKED
错误,并将消息写入错误日志:
Access denied for user 'user_name'@'host_name'.
Account is locked.
锁定帐户不会影响使用假定锁定帐户身份的代理用户进行连接的能力。它也不会影响执行具有DEFINER
属性命名锁定帐户的存储程序或视图的能力。也就是说,锁定帐户不会影响使用代理帐户或存储的程序或视图的能力。
帐户锁定功能取决于mysql.user
系统表中account_locked
列的存在。对于从5.7.6之前的MySQL版本进行的升级,请执行MySQL升级过程以确保该列存在。对于没有account_locked列的未升级安装,服务器会将所有帐户视为已解锁,并且使用ACCOUNT LOCK
或ACCOUNT UNLOCK
子句会产生错误。