在安裝完成MySQL后,我們通常添加擁有相應權限的普通用戶用來訪問數據庫。在使用普通用戶本地登錄數據庫的時候,經常會出現怎么登錄也無法登錄的情況。
例如,我的MySQL中的用戶為:
mysql> SELECT User, Host, Password FROM mysql.user; +------+------------------+-------------------------------------------+ | User | Host | Password | +------+------------------+-------------------------------------------+ | root | localhost | *84BB5DF4823DA319BBF86C99624479A198E6EEE9 | | root | liao.localdomain | *84BB5DF4823DA319BBF86C99624479A198E6EEE9 | | root | 127.0.0.1 | *84BB5DF4823DA319BBF86C99624479A198E6EEE9 | | GaMe | % | *84BB5DF4823DA319BBF86C99624479A198E6EEE9 | | root | % | *84BB5DF4823DA319BBF86C99624479A198E6EEE9 | | | localhost | | | | liao.localdomain | | +------+------------------+-------------------------------------------+
可以看到,我的數據庫中有root用戶,GaMe用戶和匿名用戶,GaMe用戶的主機使用%代表所有主機。
GaMe用戶的密碼是'redhat', 匿名用戶的密碼為空
在本機使用GaMe賬戶登錄數據庫:
[root@liao ~]# mysql -uGaMe -p'redhat' ERROR 1045 (28000): Access denied for user 'GaMe'@'localhost' (using password: YES) [root@liao ~]# mysql -uGaMe -p'redhat' ERROR 1045 (28000): Access denied for user 'GaMe'@'localhost' (using password: YES)
結果是無論如何都提示登錄被拒絕。
再次使用GaMe賬戶登錄數據庫,這次我使用空密碼嘗試
[root@liao ~]# mysql -uGaMe -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 59 Server version: 5.5.21-log Source distribution Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
登錄成功了,使用USER()和CURRENT_USER()兩個函數查看所使用的用戶。
USER()函數返回你在客戶端登陸時指定的用戶名和主機名。
CURRENT_USER()函數返回的是MySQL使用授權表中的哪個用戶來認證你的登錄請求。
mysql> SELECT USER(), CURRENT_USER(); +----------------+----------------+ | USER() | CURRENT_USER() | +----------------+----------------+ | GaMe@localhost | @localhost | +----------------+----------------+ 1 row in set (0.00 sec)
這里發現,我使用'GaMe'@'localhost'這個賬戶登錄數據庫(因為在本地登陸時沒指定主機,默認是以localhost登錄),但是數據庫使用的是''@'localhost'這個賬戶來進行登錄認證,而''@'localhost'這個匿名用戶是沒有密碼的,因此我輸入空密碼登錄成功了。但是登錄后,所對應的用戶的匿名用戶。
一般在MySQL在安裝完畢后,我們使用mysql_install_db這個腳本生成授權表,會默認創建''@'localhost'這個匿名用戶。正是因為這個匿名用戶,影響了其他用戶從本地登錄的認證。
那么MySQL是如何進行用戶身份認證呢?
通過Google查的資料,總結MySQL的簡要認證算法如下:
- 當用戶從客戶端請求登陸時,MySQL將授權表中的條目與客戶端所提供的條目進行比較,包括用戶的用戶名,密碼和主機。
- 授權表中的Host字段是可以使用通配符作為模式進行匹配的,如test.example.com, %.example.com, %.com和%都可以匹配test.example.com這個主機。
- 授權表中的User字段不允許使用模式匹配,但是可以有一個空字符的用戶名代表匿名用戶,並且空字符串可以匹配所有的用戶名,就像通配符一樣。
- 當user表中的Host和User有多個值可以匹配客戶端提供的主機和用戶名時,MySQL將user表讀入內存,並且按照一定規則排序,按照排序規則讀取到的第一個匹配客戶端用戶名和主機名的條目對客戶端進行身份驗證。
排序規則:
- 對於Host字段,按照匹配的精確程度進行排序,越精確的排序越前,例如當匹配test.example.com這個主機時, %.example.com比%.com更精確,而test.example.com比%.example.com更精確。
- 對於User字段,非空的字符串用戶名比空字符串匹配的用戶名排序更靠前。
- User和Host字段都有多個匹配值,MySQL使用主機名排序最前的條目,在主機名字段相同時再選取用戶名排序更前的條目。
- 因此,如果User和Host字段都有多個匹配值,主機名最精確匹配的條目被用戶對用戶進行認證。
了解了這個認證流程,就知道為什么GaMe登錄失敗了。
使用GaMe在本機登錄數據時,不指定-h參數默認為localhost主機登錄,而在MySQL中有兩個匹配的條目:
- 'GaMe'@'%'
- ''@'localhost'
匿名用戶能夠匹配的原因上面說過,空字符串可以匹配所有的用戶名,就像通配符一樣。
根據MySQL認證時的排序規則,第一個條目的用戶名排序更前,第二個條目的主機名更精確,排序更前。
而MySQL會優先使用主機名排序第一的條目進行身份認證,因此''@'localhost'被用戶對客戶端進行認證。因此,只有使用匿名用戶的空密碼才能登錄進數據庫。就會出現下面的情況了。
mysql> SELECT USER(), CURRENT_USER(); +----------------+----------------+ | USER() | CURRENT_USER() | +----------------+----------------+ | GaMe@localhost | @localhost | +----------------+----------------+ 1 row in set (0.00 sec)
解決的方法:刪除匿名用戶(僅僅為了安全也有這個必要)
為什么root用戶不會受影響,而只有普通用戶不能從本地登錄?
因為mysql_install_db腳本會在授權表中生成'root'@'localhost'這個賬戶。同樣的,使用root登錄MySQL時,'root'@'localhost'和''@'localhost'都能匹配登錄的賬戶,但是根據排序規則,主機名相同,而用戶名非空字符串優先,因此'roo'@'localhost'這個條目的排序更靠前。使用root本地登錄是不會被匿名用戶遮蓋。