用户需要通过账号连接到MySQL Server,本文总结了MySQL账号的常用管理操作。
目录
MySQL的账号信息存储在mysql.user表中,该表中包含了账号名称,密码,权限等相关信息,可以通过desc mysql.user命令来查看表的结构:
desc mysql.user;

注意该表的主键是Host,User组成的复合主键,这也代表了MySQL账号的组成结构,MySQL的账号由User和Host两部分组成,其格式为'User'@'Host',其中Host部分限制了用户可以登录的地址。这也意味着用户名相同并不代表是同一账号,'abc'@'host1'和'abc'@'host2'是两个完全无关的账号。
在登录的时候,MySQL需要对我们的账号进行验证,这个验证的操作是由专门的认证插件来完成的。通过show plugins我们可以看到MySQL的认证插件(Type为Authentication):
show plugins;

同时在我们创建账号时,认证插件会采用哈希算法对密码进行加密,并存储到mysql.user的authentication_string字段(如果是旧的MySQL版本,这个字段也可能是password):
select user,host,plugin,authentication_string from mysql.user where user='root';

上面查询中,plugin字段代表该账号登录时使用的验证插件。
MySQL的3种认证插件简介如下:
注意:如果MySQL从低版本升级到8.0时,由于默认认证插件的变化,可能导致之前的应用无法连接到数据库,此时可以配置SSL连接,或者将账号的认证插件降级到mysql_native_password保持兼容。
用create user语句创建新的账号,通过identified by子句设置密码:
create user 'vincent'@'localhost' identified by 'password';

上述语句创建了一个用户名为vincent的账号,并且限制只能从本地(localhost)登录。这里的主机名也可以用IP地址、网段、域名等替代:
- create user 'vincent'@'192.168.3.8' identified by 'password';
-
- create user 'vincent'@'192.168.1.%' identified by 'password';
-
- create user 'vincent'@'%.example.com' identified by 'password';

虽然账号名都是vincent,但他们都是独立的账号,只能从限定的Host登录,其中%代表通配符,如果主机名用%代替,那么则代表该用户可以从任何地方登录。
如果在创建账号时忽略了Host部分,则MySQL默认该账号可以从任何地方登录(host被设置为%):
- create user 'vincent' identified by 'password';
-
- select user,host from mysql.user where user='vincent';

在创建账号时,账号的认证插件会使用系统的默认设置(由参数 default_authentication_plugin控制):
show variables like 'default_authentication_plugin';

你也可以在创建账号时使用with ‘plugin’子句显式指定认证插件:
create user 'vincent'@'192.168.3.8' identified with 'sha256_password' by 'password';

上面显示指定了sha256_password作为改账号的认证插件。
当账号刚创建时,默认只有一个USAGE权限,即仅可以连接到服务器。你还需要为账号进行赋权才可以使用,赋权时要遵循最小适用原则,即仅对用户赋予满足其需求的最小权限。
MySQL的权限有很多,可以通过show privileges命令查看所有的权限,权限后面有相应的注释:
show privileges;

上述命令查看的是权限明细,你也可以用关键字all来替代所有权限,all的权限非常高,慎用。即使要使用,也应限制在一定的范围内。
权限的赋予是通过grant语句完成的,语句格式为:grant '权限' on '对象' to '账号';
我们先建一个测试数据库和表:
- create database mydb;
-
- use mydb;
-
- create table mytable(id int primary key, name varchar(32));

下面演示几种常用的赋权操作:
可以用db_name.*来对某数据库下所有对象统一赋权:
grant select on mydb.* to 'vincent'@'localhost';

mydb.* 代表了mydb数据下所有的对象,上面语句赋予了查询该数据库下所有对象的权限。
如果有多项权限需要赋予,可以用逗号分隔:
grant insert,delete,update on mydb.* to 'vincent'@'localhost';

上述语句赋予了mydb数据库下所有对象的增、删、改权限。
给用户赋予数据库下所有对象(mydb.*)上的所有权限(all):
grant all on mydb.* to 'vincent'@'localhost';

权限all代表了数据库mydb下的所有权限,使用时要小心。
如果在赋权的语句后面跟上with grant option,则该用户可以继续为其他用户赋权,可能导致权限泛滥,不推荐使用:
grant all on mydb.* to 'vincent'@'localhost' with grant option;

有些时候,我可能想限制用户只能查询特定的表,我们可以用db_name.table来将权限限制在表级别:
给用户'vincent'@'localhost'赋予数据库mydb下mytable表的读取权限:
grant select on mydb.mytable to 'vincent'@'localhost';

如果我想将用户的选项限定到列,可以在相应的权限后指定列名:
给用户'vincent'@'localhost'赋予数据库mydb下mytable表name列的查询和更新权限:
grant select(id,name), update(name) on mydb.mytable to 'vincent'@'localhost';

上述select(id,name), update(name),将查询权限限定在id,name列,将更新权限限定在name列。
如果要赋予存储过程或函数的相关权限,只需要带上procedure或function关键字即可:
- grant execute on procedure sys.execute_prepared_stmt to 'vincent'@'localhost';
-
- grant execute on function sys.version_patch to 'vincent'@'localhost';

赋权后,我们可以通过show grants for '账号',来查询某账号被赋予权限:
show grants for 'vincent'@'localhost';

如果仅执行show grants; 命令(没有for子句),那么就是查询自己的权限。
权限回收是通过revoke语句完成的,格式和赋权相同,只是将grant关键字替换为revoke,to关键字替换为from。格式为:revoke '权限' on '对象' from '账号';
revoke execute on procedure sys.execute_prepared_stmt from 'vincent'@'localhost';

上面的示例都是针对账号直接赋权,如果有很多用户有相似的权限,那么为每个用户独立赋权就很麻烦了,这种场景可以利用角色(role)来集中管理权限。
角色(role)是权限的集合,你可以将权限赋给角色,然后将角色赋给账号,这会方便权限的集中管理,如果涉及通用权限调整,只需要调整角色的权限即可,部分用户如果需要特别的权限,可以单独赋予。
下面创建2个角色,分别对应普通用户组和管理员组:
create role user_group, admin_group;

普通组只有mydb下查询权限,管理员组具有所有权限:
- grant select on mydb.* to user_group;
-
- grant all on mydb.* to admin_group;

最后只要将角色赋给相应的账号即可:
grant user_group to 'vincent'@'localhost';

在建立账号时,我们会指定密码,有时候这可能是一个初始密码,需要用户自己去修改。
修改自己的密码通过set password语句来修改自己的密码,语句格式为:set password='密码';
下面的语句将自己账号的密码修改为vincent:
set password='vincnet';

如果你有权限替别人修改密码,可以用set password for或者alter user语句来修改别人的密码:
- set password for 'vincent'@'localhost'='password';
-
- alter user 'vincent'@'localhost' identified by 'password';

上面两个语句的效果是相同的,任意选择一种即可。
某些场景我们需要强制用户修改密码,只需要将用户密码的状态设置为"过期",当密码的状态为过期时,账号允许连接至数据库,但是在修改密码前不会允许其他操作,由此来强制用户修改密码。
将单一账号密码状态设置为过期:
alter user 'vincent'@'localhost' password expire;

如果需要将一批账号同时设置为过期,可以通过update语句将myql.user表的password_expired字段批量设置为'Y',效果是一样的:
- update mysql.user set password_expired='Y' where user='vincent' and host='localhost';
-
- flush privileges;

update之后记得flush privileges重载权限表,否则不会生效。如果用alter user则不需要。
'vincent'@'localhost'密码失效后,执行其他语句报错(提示修改密码),只有修改密码后,限制才解除:
- show databases;
-
- set password='password';
-
- show databases;

通常情况下,MySQL是不会要求密码强度的,用户可以任意输入简单密码。如果要限制密码满足一定的强度规则,我们可以利用MySQL自带的validate_password插件来控制。
默认该插件是没有安装的,我们要先安装该插件:
install plugin validate_password soname 'validate_password.so';

插件的目录可以通过变量plugin_dir来查看,在操作系统的该目录下,我们可以搜到该插件:
select @@plugin_dir;

- cd /usr/local/mysql/lib/plugin/
-
- ll | grep validate_password

插件安装好之后,我们就可以查看相关的控制变量了,通过修改变量的值,可以控制修改密码的强度要求:
show variables like 'validate_password%';

各个变量含义说明如下:
值为low时,仅检测一项密码长度。
值为medium时,检查密码长度,混合大小写,数字数量和特殊字符4项。
值为strong时,还会额外增加限制,密码不能与字典值文件(validate_password_dictionary_file)中密码相同,用来排除设置某些密码。
我们可以根据自己的需要修改这些,变量的值:
- set global validate_password_length=10;
-
- set global validate_password_mixed_case_count=2;
-
- set global validate_password_number_count=2;

这里我修改了密码最小长度为10,必须有2个大小写混合,2个数字。
为了防止重启后丢失,推荐将其放到配置文件[mysqld]模块中:
- [mysqld]
- plugin-load-add=validate_password.so
- validate_password_length=10
- validate_password_mixed_case_count=2
- validate_password_number_count=2
- validate_password_special_char_count=1

完成上面的配置后,密码强度控制就已经生效了。这些规则只会影响未来修改密码或者新建用户的操作,并不影响已有账户。
给vincent账号改一个简单的密码,提示密码不满足当前策略:
set password for 'vincent'@'localhost'='weakpassword';

可以通过函数validate_password_strength来评估密码的强度(0最弱,100最强):
- select validate_password_strength('abc');
-
- select validate_password_strength('Asda@#9asC7U');

只能设置满足强度的密码:
set password for 'vincent'@'localhost'='12aaAA@890';

密码'12aaAA@890',长度10位,包含5个数字,2个小写字母,2个小写字母,一个特殊字符,满足强度要求,修改成功。