問題背景
用wordpress搭博客,數據庫采用MySQL。為了調試方便,創建賬戶my_account ,允許它從任意主機訪問數據庫。
CREATE USER `my_account`@'%' IDENTIFIED BY 'my_password';
修改 wp-config.php 相應配置,注意 DB_HOST 設置為 127.0.0.1。
define('DB_USER', 'my_account'); // 賬號
define('DB_PASSWORD', 'my_password'); // 密碼
define('DB_HOST', '127.0.0.1'); // 數據服務地址
部署到雲服務器上,本地瀏覽器訪問博客,提示數據庫拒絕訪問(本地連接遠程數據沒問題),以下為錯誤日志。
ERROR 1045 (28000): Access denied for user 'my_account'@'localhost' (using password: YES)`
簡單排查后,解決了問題,這里記錄下解決方案,以及出錯的原因。
解決方案
1、方案一:刪除 mysql.user表 中,Host字段為 localhost 的匿名賬號(賬戶名為空)。
2、方案二:創建 my_account@localhost 賬戶,用於本地連接數據庫。
筆者采用了方案 一。
首先,確認下 mysql.user 表中是否存在匿名賬戶。
MariaDB [(none)]> SELECT User, Host from mysql.user WHERE Host = 'localhost' AND User = '';
+------+-----------+
| User | Host |
+------+-----------+
| | localhost |
+------+-----------+
1 row in set (0.00 sec)
接着,刪除相應匿名賬戶,再次嘗試登陸,成功。
MariaDB [(none)]> DROP USER ''@'localhost';
Query OK, 0 rows affected (0.00 sec)
問題分析
為什么匿名賬戶會導致數據庫連接失敗?
需要對MySQL的賬戶創建、客戶端連接校驗有一定的了解。
創建MySQL賬戶
基礎語法如下:
CREATE USER 賬戶名@主機 IDENTIFIED BY 密碼;
注意點:(以下用 User 指代賬戶名,Host 指代 主機)
- Host 表示 允許賬戶從哪台主機訪問數據庫。主要用於做安全限制,可以是 主機名、IP地址、%(通配符);
- User 允許重復,只要 Host 是不同的就行。
- 當 Host 設置為 % 時,表示允許從任意主機連接數據庫。
比如,存在兩個xiaoming賬戶,一個允許從本機連接數據庫,一個允許從 14.215.177.39 連接數據庫。
MariaDB [(none)]> SELECT User, Host FROM mysql.user WHERE User = 'xiaoming';
+---------+---------------+
| User | Host |
+---------+---------------+
| xiaoming | 14.215.177.39 |
| xiaoming | localhost |
+---------+---------------+
2 rows in set (0.00 sec)
匿名賬戶
也就是 User 為空的賬戶,可以匹配任意用戶名。如下指令創建了匿名賬戶。
CREATE USER ''@'localhost' IDENTIFIED BY 'pwd3';
身份校驗
數據庫服務器收到客戶端連接,首先會進行身份校驗,將 User、Host、Password 字段,跟 mysql.user 表里的記錄進行比較,確認是否合法賬戶。
這里有個問題:如果 mysql.user表 中存在多條匹配記錄,該以哪條記錄為准?
答案是“優先級”。大致規則如下:
- 首先,檢查 Host 字段。如果有多個 Host 符合條件,則選擇匹配度最高的記錄(IP地址 > 通配符%)。
- 其次,檢查 User 字段。如果有多個 User 符合條件,則選擇匹配度最高的記錄。匿名用戶可以匹配任何用戶,因此匹配度最低。
優先級匹配例子
舉例,假設本地數據庫有如下兩個賬戶(Password字段實際非明文)。
+------------+-----------+-----------+
| User | Host | Password |
+------------+-----------+-----------+
| my_account | % | 123 |
| | localhost | 456 |
+------------+-----------+-----------+
運行如下命令,最終登錄的賬戶,匹配的是 第2條 記錄。(讀者可自行嘗試,輸入密碼123登錄失敗,輸入456登錄成功)
mysql -u my_account -p
為什么呢?回顧下匹配的優先級。
- 首先,檢查 Host 字段。localhost、% 都符合要求。localhost 匹配度高於 %,因此匹配到第2條記錄。
- 接着,檢查 User 字段。第2條記錄是匿名賬戶,可匹配任意User值,因此,第2條記錄符合要求。
因此,雖然賬戶 my_account 的 Host字段 為%,但是在本地(數據庫所在主機)連接數據庫時,因為上述規則的存在,MySQL 會認為你是用匿名賬戶在登錄。
my_account 跟匿名賬戶的密碼是不同的,因此密碼校驗不通過,拒絕訪問。
寫在后面
因匿名用戶存在,導致本地連接數據庫拒絕連接,這個問題很常見,有不少人認為這是MySQL的設計缺陷,比如 這里。
了解了MySQL的身份驗證邏輯,遇到類似問題也就有思路了。關於%通配符,匹配優先級,上文並沒有詳細展開,感興趣的讀者可以自行查看官方文檔。
另外,MySQL拒絕訪問的原因有多種,讀者應具體問題具體分析。
如有錯漏,敬請指出。
相關鏈接
Host wildcard % which is said in docs that means "all hosts" excludes localhost
