某运维人员通过脚本批量创建 MySQL 用户时,发现一个奇怪现象:应用程序使用自动生成的密码能正常访问数据库,但手动使用mysql客户端登录时却提示 "Access denied"。经过排查,异常用户的密码均包含特殊字符$,如密码abc$2UY。
-
脚本创建用户:
简化的创建脚本如下,密码变量用双引号定义:
-
登录测试:
- 手动输入密码
abc$2UY:登录失败(ERROR 1045)。
- 使用命令行带密码(单引号包裹):
mysql -h127.0.0.1 -uapp -p'abc$2UY',登录失败。
- 命令行不带引号或用双引号:
mysql -h127.0.0.1 -uapp -pabc$2UY,登录成功。
问题的核心在于Shell 对不同引号的处理逻辑:
- 双引号(" "):会解析字符串中的变量(如
$var)和命令(如$(cmd)),将其替换为实际值后再执行。
- 单引号(' '):将字符串视为纯文本,不解析任何变量或特殊字符,原样保留。
在上述脚本中,密码pw="abc$2UY"用双引号定义,Shell 会将$2视为位置参数(类似脚本传入的第二个参数)。由于脚本未传入参数,$2被解析为空,实际传递给 MySQL 的密码为abcUY(而非预期的abc$2UY)。
- 存储的密码:脚本执行后,MySQL 中实际存储的密码是
abcUY(因$2被解析为空)。
- 手动登录的密码:用户输入
abc$2UY时,Shell 未解析$2(或用单引号保护),导致输入的密码与存储的abcUY不匹配,登录失败。
- 不带引号的登录:
mysql -pabc$2UY中,Shell 解析$2为空,实际传递的密码是abcUY,与存储值一致,因此登录成功。
login-path是 MySQL 的免密登录配置工具,但在处理含#的密码时存在历史 bug:
- 问题表现:若密码为
123#abc,直接配置login-path会失败,因#在配置中被视为注释符。
- 临时解决方案:在输入密码时用双引号包裹(如输入
"123#abc")。
- 版本修复:该 bug 在 MySQL 5.7.33 和 8.0.23 版本中被修复,之后版本无需额外处理。
除$和#外,!、&、*等特殊字符也可能被 Shell 解析,导致密码异常:
- 例如,密码
pass!word在 Shell 中,!可能被解析为历史命令调用,需用单引号保护('pass!word')。
- 优先避免特殊字符:创建密码时尽量不使用
$、#、!等易被解析的字符,减少复杂度。
- 版本适配:若使用
login-path且密码含#,确保 MySQL 版本为 5.7.33 + 或 8.0.23+,无需额外处理;旧版本需用双引号包裹密码。
- 密码验证:创建用户后,通过
select user,host,authentication_string from mysql.user where user='用户名';查看加密后的密码,与预期密码的加密结果对比(可手动创建相同密码的测试用户验证)。