在创建 MySQL 用户时使用% 作为主机

我的 MySQL 数据库需要两个用户: appuser 和 support。
One of the application developers insists that I create four accounts for these users:

appuser@'%'
appuser@'localhost'
support@'%'
support@'localhost'

我真搞不懂他为什么觉得我们需要这个。使用通配符作为主机不会照顾“ localhost”吗?

有什么想法吗?

(在此使用 MySQL 5.5)

122381 次浏览

localhost is special in MySQL, it means a connection over a UNIX socket (or named pipes on Windows, I believe) as opposed to a TCP/IP socket. Using % as the host does not include localhost, hence the need to explicitly specify it.

如果要从本地主机连接到 user@'%',请使用 mysql -h192.168.0.1 -uuser -p

正如@nos 在当前对这个问题的公认答案的评论中指出的那样,公认的答案是不正确的。

是的,当通过套接字连接而不是标准的 TCP/IP 连接进行连接时,对用户帐户主机使用 %localhost是有区别的。

%的主机值不包括用于套接字的 localhost,因此如果要使用该方法进行连接,则必须指定 localhost

将提供一个稍微不同的答案,到目前为止提供的。

如果您的用户表 ''@'localhost'中有一行来自 localhost 的匿名用户,那么这将被视为比使用通配符的主机 'user'@'%'的用户更具体。这就是为什么还需要提供 'user'@'localhost'的原因。

您可以在 这一页的底部看到更详细的解释。

我们测试一下。

作为超级用户连接,然后:

SHOW VARIABLES LIKE "%version%";
+-------------------------+------------------------------+
| Variable_name           | Value                        |
+-------------------------+------------------------------+
| version                 | 10.0.23-MariaDB-0+deb8u1-log |

然后

USE mysql;

Setup

用密码 bar创建一个用户 foo进行测试:

CREATE USER foo@'%' IDENTIFIED BY 'bar'; FLUSH PRIVILEGES;

连接

To connect to the Unix Domain Socket (i.e. the I/O pipe that is named by the filesystem entry /var/run/mysqld/mysqld.sock or some such), run this on the command line (use the --protocol option to make doubly sure)

mysql -pbar -ufoo
mysql -pbar -ufoo --protocol=SOCKET

人们期望上述匹配“ user comes from localhost”,但肯定不匹配“ user comes from 127.0.0.1”。

若要从“127.0.0.1”连接到服务器,请在命令行上运行此命令

mysql -pbar -ufoo --bind-address=127.0.0.1 --protocol=TCP

如果你省略了 --protocol=TCP,那么 mysql命令仍然会尝试使用 Unix domain socket。你也可以说:

mysql -pbar -ufoo --bind-address=127.0.0.1 --host=127.0.0.1

The two connection attempts in one line:

export MYSQL_PWD=bar; \
mysql -ufoo --protocol=SOCKET --execute="SELECT 1"; \
mysql -ufoo --bind-address=127.0.0.1 --host=127.0.0.1 --execute="SELECT 1"

(在环境中设置密码,以便将其传递给 mysql进程)

可疑情况下的验证

To really check whether the connection goes via a TCP/IP socket or a Unix Domain socket

  1. 通过检查 ps faux的输出获得 mysql 客户端进程的 PID
  2. 运行 lsof -n -p<yourpid>

You will see something like:

mysql [PID] quux 3u IPv4 [code] 0t0 TCP 127.0.0.1:[port]->127.0.0.1:mysql (ESTABLISHED)

或者

mysql [PID] quux 3u unix [code] 0t0 [code] socket

所以:

案例0: Host =’10.10.10.10’(null test)

update user set host='10.10.10.10' where user='foo'; flush privileges;
  • Connect using socket: FAILURE
  • 从127.0连接。0.1: 失败

案例1: Host =’%’

update user set host='%' where user='foo'; flush privileges;
  • 使用插座连接: OK
  • 从127.0连接。0.1: OK

案例2: Host = ‘ localhost’

update user set host='localhost' where user='foo';flush privileges;

行为变化 ,这显然取决于 skip-name-resolve。如果设置了,则根据日志忽略具有 localhost的行。在错误日志中可以看到以下内容: “‘ user’条目‘ root@localhost’在——略过名称解析模式中被忽略”。这意味着不能通过 Unix domain socket 连接。但事实并非如此。Abc1现在意味着只有 Unix domain socket,不再匹配127.0.0.1。

skip-name-resolve关闭:

  • 使用插座连接: OK
  • 从127.0连接。0.1: OK

skip-name-resolve is on:

  • 使用插座连接: OK
  • 从127.0连接。0.1: 失败

案例3: Host =’127.0.0.1’

update user set host='127.0.0.1' where user='foo';flush privileges;
  • 使用插座连接: 失败
  • 从127.0连接。0.1: OK

案例4: Host =”

update user set host='' where user='foo';flush privileges;
  • 使用插座连接: OK
  • 从127.0连接。0.1: OK

(根据 MySQL 5.7:6.2.4访问控制阶段1: 连接验证“空字符串”也表示“任何主机”,但在“%”之后进行排序。)

Case 5: Host = '192.168.0.1' (extra test)

(“192.168.0.1”是我机器的 IP 地址之一,在您的情况下适当更改)

update user set host='192.168.0.1' where user='foo';flush privileges;
  • 使用插座连接: 失败
  • 从127.0连接。0.1: 失败

但是

  • 使用 mysql -pbar -ufoo -h192.168.0.1连接: OK (!)

后者是因为这实际上是来自 192.168.0.1的 TCP 连接,正如 lsof所揭示的:

TCP 192.168.0.1:37059->192.168.0.1:mysql (ESTABLISHED)

边缘情况 A: Host =’0.0.0.0’

update user set host='0.0.0.0' where user='foo';flush privileges;
  • 使用插座连接: 失败
  • 从127.0连接。0.1: 失败

边缘情况 B: Host =’255.255.255.255’

update user set host='255.255.255.255' where user='foo';flush privileges;
  • 使用插座连接: 失败
  • Connect from 127.0.0.1: FAILURE

边缘情况 C: Host =’127.0.0.2’

(127.0.0.2是完全有效的回送地址,相当于 RFC6890中定义的127.0.0.1)

update user set host='127.0.0.2' where user='foo';flush privileges;
  • Connect using socket: FAILURE
  • 从127.0连接。0.1: 失败

有趣的是:

  • mysql -pbar -ufoo -h127.0.0.2127.0.0.1连接,故障
  • mysql -pbar -ufoo -h127.0.0.2 --bind-address=127.0.0.2没问题

Cleanup

delete from user where user='foo';flush privileges;

Addendum

要查看权限表之一 mysql.user表中的实际内容,请使用:

SELECT SUBSTR(password,1,6) as password, user, host,
Super_priv AS su,
Grant_priv as gr,
CONCAT(Select_priv, Lock_tables_priv) AS selock,
CONCAT(Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv) AS modif,
CONCAT(References_priv, Index_priv, Alter_priv) AS ria,
CONCAT(Create_tmp_table_priv, Create_view_priv, Show_view_priv) AS views,
CONCAT(Create_routine_priv, Alter_routine_priv, Execute_priv, Event_priv, Trigger_priv) AS funcs,
CONCAT(Repl_slave_priv, Repl_client_priv) AS replic,
CONCAT(Shutdown_priv, Process_priv, File_priv, Show_db_priv, Reload_priv, Create_user_priv) AS admin
FROM user ORDER BY user, host;

这给出了:

+----------+----------+-----------+----+----+--------+-------+-----+-------+-------+--------+--------+
| password | user     | host      | su | gr | selock | modif | ria | views | funcs | replic | admin  |
+----------+----------+-----------+----+----+--------+-------+-----+-------+-------+--------+--------+
| *E8D46   | foo      |           | N  | N  | NN     | NNNNN | NNN | NNN   | NNNNN | NN     | NNNNNN |

mysql.db也是如此:

SELECT host,db,user,
Grant_priv as gr,
CONCAT(Select_priv, Lock_tables_priv) AS selock,
CONCAT(Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv) AS modif,
CONCAT(References_priv, Index_priv, Alter_priv) AS ria,
CONCAT(Create_tmp_table_priv, Create_view_priv, Show_view_priv) AS views,
CONCAT(Create_routine_priv, Alter_routine_priv, Execute_priv) AS funcs
FROM db ORDER BY user, db, host;