用户管理
导学
数据库安全的第一道防线是用户管理。MySQL 5.7 提供了完善的用户认证和账号管理机制,理解它才能避免"所有人用 root"的安全灾难。
定义
用户(User):MySQL 中由用户名和主机名共同标识的账号,格式为 '用户名'@'主机名'。用户的认证信息(密码插件、密码哈希)存储在 mysql.user 系统表中。
创建用户
基础创建
第一步:查看当前所有用户
SELECT user, host, plugin, password_expired
FROM mysql.user
ORDER BY user, host;
当前数据状态(mysql.user 部分字段):
| user | host | plugin | password_expired |
|---|---|---|---|
| root | localhost | mysql_native_password | N |
| mysql.session | localhost | mysql_native_password | N |
| mysql.sys | localhost | mysql_native_password | N |
第二步:创建基础用户
-- 创建只能从本机连接的用户
CREATE USER 'app_user'@'localhost'
IDENTIFIED BY 'StrongPassword123!';
-- 创建只能从指定网段连接的用户
CREATE USER 'app_user'@'192.168.1.%'
IDENTIFIED BY 'StrongPassword123!';
-- 创建可以从任何主机连接的用户(最不安全,生产环境慎用)
CREATE USER 'app_user'@'%'
IDENTIFIED BY 'StrongPassword123!';
第三步:确认用户创建成功
SELECT user, host, plugin, password_expired
FROM mysql.user
WHERE user LIKE 'app_user'
ORDER BY user, host;
操作后的数据状态:
| user | host | plugin | password_expired |
|---|---|---|---|
| app_user | % | mysql_native_password | N |
| app_user | 192.168.1.% | mysql_native_password | N |
| app_user | localhost | mysql_native_password | N |
结果解读:
- 同一个用户名
app_user搭配不同的主机名,形成了三个完全独立的账号。 'app_user'@'localhost'只能从数据库本机连接。'app_user'@'192.168.1.%'只能从 192.168.1.x 网段连接,常用于内网应用服务器。'app_user'@'%'允许从任何 IP 连接,安全风险最高,生产环境应避免对高权限账号使用。- MySQL 5.7 默认使用
mysql_native_password认证插件。
高级创建(指定插件和过期策略)
第一步:创建带密码过期策略的用户
CREATE USER 'admin_user'@'localhost'
IDENTIFIED WITH mysql_native_password BY 'AdminPass456!'
PASSWORD EXPIRE INTERVAL 90 DAY;
第二步:确认创建结果
SELECT user, host, plugin, password_expired, password_lifetime
FROM mysql.user
WHERE user = 'admin_user';
操作后的数据状态:
| user | host | plugin | password_expired | password_lifetime |
|---|---|---|---|---|
| admin_user | localhost | mysql_native_password | N | 90 |
结果解读:
IDENTIFIED WITH mysql_native_password显式指定认证插件。PASSWORD EXPIRE INTERVAL 90 DAY设置密码 90 天后过期,过期后用户登录时必须修改密码。password_lifetime = 90表示该用户密码的生命周期为 90 天。
修改用户
修改密码
第一步:查看修改前的用户状态
SELECT user, host, password_expired, password_last_changed
FROM mysql.user
WHERE user = 'app_user' AND host = 'localhost';
当前数据状态:
| user | host | password_expired | password_last_changed |
|---|---|---|---|
| app_user | localhost | N | 2026-06-10 15:00:00 |
第二步:修改密码
ALTER USER 'app_user'@'localhost'
IDENTIFIED BY 'NewPassword456!';
第三步:确认密码已修改
SELECT user, host, password_last_changed
FROM mysql.user
WHERE user = 'app_user' AND host = 'localhost';
操作后的数据状态:
| user | host | password_last_changed |
|---|---|---|
| app_user | localhost | 2026-06-10 15:05:00 |
结果解读:
password_last_changed字段更新为当前时间,表明密码已被修改。- 修改密码后,已建立的连接不会断开,但新连接需要使用新密码。
强制密码过期
第一步:设置密码过期
ALTER USER 'app_user'@'localhost' PASSWORD EXPIRE;
第二步:确认密码已过期
SELECT user, host, password_expired
FROM mysql.user
WHERE user = 'app_user' AND host = 'localhost';
操作后的数据状态:
| user | host | password_expired |
|---|---|---|
| app_user | localhost | Y |
第三步:模拟过期用户登录
当 'app_user'@'localhost' 尝试连接时:
mysql -u app_user -p
Enter password: ************
执行任何查询:
SHOW DATABASES;
结果:
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
第四步:用户重置密码
ALTER USER USER() IDENTIFIED BY 'ResetPassword789!';
第五步:确认密码状态恢复
SELECT user, host, password_expired
FROM mysql.user
WHERE user = 'app_user' AND host = 'localhost';
操作后的数据状态:
| user | host | password_expired |
|---|---|---|
| app_user | localhost | N |
结果解读:
password_expired = Y表示用户密码已过期,下次登录后必须修改密码才能执行任何操作。- 这常用于安全策略:首次创建用户、定期强制更换密码、或怀疑密码泄露时。
- 用户登录后执行
ALTER USER USER() IDENTIFIED BY '新密码'即可解除过期状态。
锁定和解锁用户
第一步:锁定用户
ALTER USER 'app_user'@'localhost' ACCOUNT LOCK;
第二步:确认用户已锁定
SELECT user, host, account_locked
FROM mysql.user
WHERE user = 'app_user' AND host = 'localhost';
操作后的数据状态:
| user | host | account_locked |
|---|---|---|
| app_user | localhost | Y |
第三步:模拟锁定用户登录
mysql -u app_user -p
Enter password: ************
结果:
ERROR 3118 (HY000): Access denied for user 'app_user'@'localhost'. Account is locked.
第四步:解锁用户
ALTER USER 'app_user'@'localhost' ACCOUNT UNLOCK;
第五步:确认用户已解锁
SELECT user, host, account_locked
FROM mysql.user
WHERE user = 'app_user' AND host = 'localhost';
操作后的数据状态:
| user | host | account_locked |
|---|---|---|
| app_user | localhost | N |
结果解读:
ACCOUNT LOCK可以立即阻止用户登录,无需修改密码或删除用户。- 适用于临时停用账号(如员工离职交接期间、安全事件调查期间)。
ACCOUNT UNLOCK恢复账号正常使用,之前设置的密码和权限均保留。
删除用户
第一步:确认要删除的用户存在
SELECT user, host FROM mysql.user WHERE user = 'app_user' ORDER BY host;
当前数据状态:
| user | host |
|---|---|
| app_user | % |
| app_user | 192.168.1.% |
| app_user | localhost |
第二步:删除指定用户
DROP USER 'app_user'@'localhost';
第三步:确认用户已删除
SELECT user, host FROM mysql.user WHERE user = 'app_user' ORDER BY host;
操作后的数据状态:
| user | host |
|---|---|
| app_user | % |
| app_user | 192.168.1.% |
结果解读:
'app_user'@'localhost'已被删除,该用户在所有权限表(mysql.user、mysql.db、mysql.tables_priv等)中的记录均被清理。- 必须使用完整的
'用户名'@'主机名'格式删除,不能只写DROP USER 'app_user',否则会报错。 - 警告:绝对不要使用
DELETE FROM mysql.user WHERE user = 'xxx'删除用户,这会留下权限表中的残留记录,导致权限混乱。
完整流程示例:创建最小权限的应用用户
场景:为应用系统创建专用数据库用户
第一步:建立应用数据库和表
-- 创建应用数据库
CREATE DATABASE IF NOT EXISTS app_db CHARACTER SET utf8mb4;
USE app_db;
-- 创建员工表
CREATE TABLE employees (
emp_id INT AUTO_INCREMENT PRIMARY KEY,
emp_name VARCHAR(20),
dept VARCHAR(20),
score DECIMAL(5,2)
);
-- 插入测试数据
INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);
当前数据状态(employees 表):
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
第二步:创建应用专用用户
CREATE USER 'app_user'@'10.0.0.%'
IDENTIFIED BY 'AppPass2024!';
第三步:授予最小权限
GRANT SELECT, INSERT, UPDATE ON app_db.*
TO 'app_user'@'10.0.0.%';
第四步:验证权限生效
-- 查看用户权限
SHOW GRANTS FOR 'app_user'@'10.0.0.%';
权限验证结果:
| Grants for app_user@10.0.0.% |
|---|
| GRANT USAGE ON . TO 'app_user'@'10.0.0.%' |
GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'app_user'@'10.0.0.%' |
第五步:以应用用户身份验证操作
使用 'app_user'@'10.0.0.%' 连接数据库:
-- 验证 SELECT 权限(成功)
USE app_db;
SELECT * FROM employees;
结果:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
-- 验证 INSERT 权限(成功)
INSERT INTO employees (emp_name, dept, score)
VALUES ('小崔', '技术部', 90);
-- 验证 UPDATE 权限(成功)
UPDATE employees SET score = score - 5 WHERE emp_id = 1;
-- 验证 DELETE 权限(失败,未授权)
DELETE FROM employees WHERE emp_id = 2;
结果:
ERROR 1142 (42000): DELETE command denied to user 'app_user'@'10.0.0.%' for table 'employees'
-- 验证 DROP 权限(失败,未授权)
DROP TABLE employees;
结果:
ERROR 1142 (42000): DROP command denied to user 'app_user'@'10.0.0.%' for table 'employees'
第六步:查看操作后的数据
SELECT * FROM employees;
操作后的数据状态(employees 表):
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 95 |
| 2 | 白歌 | 技术部 | NULL |
| 3 | 小崔 | 技术部 | 90 |
结果解读:
app_user用户成功执行了SELECT(查询)、INSERT(新增员工)、UPDATE(分数调整)。DELETE和DROP操作被拒绝,因为未授予这些权限。- 这体现了最小权限原则:应用用户只需要完成业务所需的权限,多余的权限一律不授予。
第七步:回收权限
-- 回收 INSERT 权限
REVOKE INSERT ON app_db.* FROM 'app_user'@'10.0.0.%';
-- 查看剩余权限
SHOW GRANTS FOR 'app_user'@'10.0.0.%';
回收后的权限状态:
| Grants for app_user@10.0.0.% |
|---|
| GRANT USAGE ON . TO 'app_user'@'10.0.0.%' |
GRANT SELECT, UPDATE ON app_db.* TO 'app_user'@'10.0.0.%' |
第八步:验证回收后的权限
以 'app_user'@'10.0.0.%' 连接数据库:
USE app_db;
-- 尝试 INSERT(失败)
INSERT INTO employees (emp_name, dept, score)
VALUES ('测试', '测试部', 1);
结果:
ERROR 1142 (42000): INSERT command denied to user 'app_user'@'10.0.0.%' for table 'employees'
-- SELECT 仍然可以执行(成功)
SELECT * FROM employees WHERE emp_id = 1;
结果:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 95 |
结果解读:
REVOKE成功回收了INSERT权限,但保留了SELECT和UPDATE。USAGE权限表示用户可以连接数据库,这是所有用户默认拥有的基础权限,无需回收。- 权限回收后,已建立的连接可能需要重新连接才能感知权限变化(取决于客户端缓存)。
第九步:清理(删除用户)
DROP USER 'app_user'@'10.0.0.%';
-- 确认已删除
SELECT user, host FROM mysql.user WHERE user = 'app_user';
结果:空集,用户已完全删除。
查看当前用户
-- 查看当前认证用户(MySQL 实际用于权限校验的用户)
SELECT CURRENT_USER();
结果示例:
| CURRENT_USER() |
|---|
| root@localhost |
-- 查看当前连接用户(包含客户端信息)
SELECT USER();
结果示例:
| USER() |
|---|
| root@localhost |
-- 查看当前数据库
SELECT DATABASE();
结果示例:
| DATABASE() |
|---|
| app_db |
结果解读:
CURRENT_USER()返回实际用于权限判断的用户身份。如果使用了代理用户(proxy user),可能与连接用户不同。USER()返回当前连接的完整用户信息,包括客户端主机名。- 当应用程序使用连接池时,这两个函数可以帮助排查权限问题,确认当前操作使用的是哪个账号。
MySQL 5.7 的密码安全增强
| 特性 | 说明 |
|---|---|
| 默认密码验证插件 | mysql_native_password(5.7 默认),caching_sha2_password(8.0 默认) |
| 密码过期策略 | 可设置密码有效期,强制定期更换 |
| 密码强度验证 | validate_password 插件强制密码复杂度 |
| 安装时生成临时密码 | 首次安装后 root 密码写入日志,必须修改后才能使用 |
查看密码策略:
SHOW VARIABLES LIKE 'validate_password%';
结果示例:
| Variable_name | Value |
|---|---|
| validate_password_check_user_name | ON |
| validate_password_dictionary_file | |
| validate_password_length | 8 |
| validate_password_mixed_case_count | 1 |
| validate_password_number_count | 1 |
| validate_password_policy | MEDIUM |
| validate_password_special_char_count | 1 |
结果解读:
validate_password_policy = MEDIUM表示密码必须包含大小写字母、数字和特殊字符。validate_password_length = 8要求密码最少 8 位。- 尝试创建弱密码会被拒绝:
CREATE USER 'weak_user'@'localhost' IDENTIFIED BY '123';
结果:
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
常见误区
| 误区 | 正解 |
|---|---|
| 用户名相同就是同一个用户 | 'user'@'localhost' 和 'user'@'%' 是完全不同的两个账号。 |
| root 可以从任何主机登录 | 默认只有 'root'@'localhost',远程 root 需额外创建或修改。 |
删除用户用 DELETE FROM mysql.user | 应该用 DROP USER,它会同时清理权限表中的相关记录。 |
面试考点
Q:MySQL 用户由什么组成?
用户名 + 主机名,如
'user'@'localhost'。相同用户名不同主机名是完全不同的账号。
Q:为什么生产环境不能用 root@%?
root 拥有最高权限,从任意主机可登录意味着一旦密码泄露,攻击者可以从任何地方控制数据库。应创建最小权限的专用账号,并限制登录 IP。
Q:MySQL 5.7 安装后 root 密码在哪?
首次安装时,临时 root 密码写入错误日志(
mysqld.log),搜索 "A temporary password is generated for root@localhost"。首次登录后必须修改密码。
小结
- MySQL 用户由用户名@主机名唯一标识
- 创建用户用
CREATE USER,修改用ALTER USER,删除用DROP USER - MySQL 5.7 增强了密码安全:过期策略、强度验证、安装时临时密码
- 生产环境遵循最小权限原则,禁止 root 远程登录
- 完整流程:创建用户 → 授予最小权限 → 验证权限边界 → 按需回收 → 最终清理
下一章引子:用户创建好了,但没有任何权限。接下来学习如何精确地授予和回收权限。