27.6 存储对象访问控制
存储程序(过程、函数、触发器和事件)和视图在使用前定义,并在引用时在安全上下文中执行,该上下文确定了存储对象的权限。存储对象的权限是由其DEFINER
属性和SQL SECURITY
特性控制的。
存储对象定义可以包含一个DEFINER
属性,该属性指定一个 MySQL 账户。如果定义省略了DEFINER
属性,则默认对象定义者是创建它的用户。
以下规则确定了可以指定为存储对象DEFINER
属性的账户:
-
如果您拥有
SET_ANY_DEFINER
特权,可以指定任何账户为DEFINER
属性。如果账户不存在,会生成警告。另外,要将存储对象DEFINER
属性设置为拥有SYSTEM_USER
特权的账户,您必须拥有SYSTEM_USER
特权。 -
否则,只能指定自己的账户,或者使用
CURRENT_USER
或CURRENT_USER()
指定账户。您不能将定义者设置为其他账户。
创建存储对象时指定不存在的DEFINER
账户将创建孤儿对象,这可能会产生负面影响;请参阅孤儿存储对象。
对于存储程序(过程和函数)和视图,对象定义可以包含一个SQL SECURITY
特性,指定执行对象的上下文为定义者或调用者上下文。如果定义省略了SQL SECURITY
特性,缺省为定义者上下文。
触发器和事件没有SQL SECURITY
特性,总是以定义者上下文执行。服务器自动根据需要 invoke 这些对象,因此没有调用用户。
定义者和调用者安全上下文不同:
考虑以下存储过程,它以SQL SECURITY DEFINER
声明,以定义者安全上下文执行:
CREATE DEFINER = 'admin'@'localhost' PROCEDURE p1()
SQL SECURITY DEFINER
BEGIN
UPDATE t1 SET counter = counter + 1;
END;
任何具有p1
的EXECUTE
权限的用户可以使用CALL
语句调用它。然而,当p1
执行时,它以定义者安全上下文执行,因此以'admin'@'localhost'
账户的权限执行。这账户必须具有p1
的EXECUTE
权限,以及对表t1
的UPDATE
权限。否则,过程将失败。
现在考虑以下存储过程,它与p1
相同,但是其SQL SECURITY
特性为INVOKER
:
CREATE DEFINER = 'admin'@'localhost' PROCEDURE p2()
SQL SECURITY INVOKER
BEGIN
UPDATE t1 SET counter = counter + 1;
END;
与p1
不同的是,p2
以调用者安全上下文执行,因此以调用者的权限执行,无论DEFINER
属性的值。p2
将失败,如果调用者缺乏p2
的EXECUTE
权限或对表t1
的UPDATE
权限。
孤儿存储对象
孤儿存储对象是指其DEFINER
属性命名的不存在账户:
-
孤儿存储对象可以通过在对象创建时指定不存在的
DEFINER
账户来创建。 -
现有的存储对象可以通过执行
DROP USER
语句或RENAME USER
语句变成孤儿存储对象。
孤儿存储对象可能会出现以下问题:
-
由于
DEFINER
账户不存在,对象可能在执行时不工作正常:-
对于存储程序,如果
SQL SECURITY
值为DEFINER
但定义账户不存在,则在程序执行时会出现错误。 -
对于触发器,不建议在账户存在之前激活触发器。否则,对于权限检查的行为是未定义的。
-
对于事件,如果账户不存在,则在事件执行时会出现错误。
-
对于视图,如果
SQL SECURITY
值为DEFINER
但定义账户不存在,则在引用视图时会出现错误。
-
-
对象可能会存在安全风险,如果不存在的
DEFINER
账户后续被重新创建用于与对象无关的目的。在这种情况下,账户“adopt”对象,并且具有适当权限,可以执行对象,即使不是预期的。
服务器实施以下账户管理安全检查,以防止可能无意地导致存储对象变成孤儿或导致当前孤儿存储对象被adopt的操作:
-
DROP USER
语句失败,如果要删除的账户是任何存储对象的DEFINER
属性命名的账户。 (即语句失败,如果删除账户将导致存储对象变成孤儿。) -
RENAME USER
语句失败,如果要重命名的账户是任何存储对象的DEFINER
属性命名的账户。 (即语句失败,如果重命名账户将导致存储对象变成孤儿。) -
CREATE USER
语句失败,如果要创建的账户是任何存储对象的DEFINER
属性命名的账户。 (即语句失败,如果创建账户将导致账户adopt当前孤儿存储对象。)
在某些情况下,可能需要故意执行那些帐户管理语句,即使它们将否失败。为了使这可能,用户拥有ALLOW_NONEXISTENT_DEFINER
特权时,该特权将覆盖孤儿对象安全检查语句将成功而不是失败。
要获取MySQL安装中存储对象定义者的帐户信息,可以查询INFORMATION_SCHEMA
。
这个查询标识哪些INFORMATION_SCHEMA
表格描述了具有DEFINER
属性的对象:
mysql> SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'DEFINER';
+--------------------+------------+
| TABLE_SCHEMA | TABLE_NAME |
+--------------------+------------+
| information_schema | EVENTS |
| information_schema | ROUTINES |
| information_schema | TRIGGERS |
| information_schema | VIEWS |
+--------------------+------------+
结果告诉你哪些表格可以查询以发现哪些存储对象DEFINER
值存在,并且哪些对象具有特定的DEFINER
值:
-
要确定每个表格中存在的
DEFINER
值,可以使用这些查询:SELECT DISTINCT DEFINER FROM INFORMATION_SCHEMA.EVENTS; SELECT DISTINCT DEFINER FROM INFORMATION_SCHEMA.ROUTINES; SELECT DISTINCT DEFINER FROM INFORMATION_SCHEMA.TRIGGERS; SELECT DISTINCT DEFINER FROM INFORMATION_SCHEMA.VIEWS;
查询结果对任何帐户的显示都是有意义的:
-
如果帐户存在,删除或重命名它将导致存储对象变为孤儿。如果您计划删除或重命名帐户,请首先删除或重命名与其关联的存储对象或将其重新定义为具有不同的定义者。
-
如果帐户不存在,创建它将导致它继承当前孤儿存储对象。如果您计划创建帐户,请考虑孤儿对象是否应该与之关联。如果不是,请将其重新定义为具有不同的定义者。
要重新定义具有不同定义者的对象,可以使用
ALTER EVENT
或ALTER VIEW
来直接修改事件和视图的DEFINER
帐户。对于存储程序和函数,以及触发器,您必须删除对象并重新创建以将不同的DEFINER
帐户分配给它。 -
-
要确定哪些对象具有给定的
DEFINER
帐户,可以使用这些查询,替换感兴趣的帐户名称为
:user_name
@host_name
SELECT EVENT_SCHEMA, EVENT_NAME FROM INFORMATION_SCHEMA.EVENTS WHERE DEFINER = 'user_name@host_name'; SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE FROM INFORMATION_SCHEMA.ROUTINES WHERE DEFINER = 'user_name@host_name'; SELECT TRIGGER_SCHEMA, TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS WHERE DEFINER = 'user_name@host_name'; SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE DEFINER = 'user_name@host_name';
对于
ROUTINES
表格,查询包括ROUTINE_TYPE
列,以便输出行区分存储程序或存储函数的DEFINER
。如果您搜索的帐户不存在,那么显示的对象都是孤儿对象。
为了减少存储对象创建和使用的风险,请遵循以下指南:
-
不要创建孤儿存储对象,即对象的
DEFINER
属性命名了不存在的帐户。不要导致存储对象变为孤儿的帐户删除或重命名任何现有对象的DEFINER
属性。 -
对于存储程序或视图,使用
SQL SECURITY INVOKER
在对象定义中,以便它只能由具有适当权限的用户使用的操作。 -
如果您使用具有
SET_ANY_DEFINER
特权的帐户创建定义上下文存储对象,请指定明确的DEFINER
属性,指定一个拥有所需操作的帐户的名称。只有在绝对必要时才指定高权限的DEFINER
帐户。 -
管理员可以通过不授予用户
SET_ANY_DEFINER
特权来防止用户创建指定高权限的DEFINER
帐户的存储对象。 -
定位上下文对象应考虑它们可能访问的数据可能没有权限。某些情况下,您可以通过不授予未经授权的用户特定的权限来防止这些对象的引用:
然而,对于触发器和事件,因为它们总是以定义上下文执行。服务器自动 invoke 这些对象,以便在必要时执行用户不直接引用它们:
-
触发器由与其关联的表的访问激活,即使是普通用户没有特殊权限的访问。
-
事件在服务器上按计划执行。
在这两种情况下,如果
DEFINER
帐户是高权限的,那么对象可能能够执行敏感或危险的操作。这仍然成立,即使撤销了创建对象的用户帐户的权限。管理员应该特别小心地授予用户对象创建权限。 -
-
默认情况下,当具有
SQL SECURITY DEFINER
特性的存储程序被执行时,MySQL Server 不会为 MySQL 帐户在DEFINER
子句中指定的帐户设置任何活动角色。只有在activate_all_roles_on_login
系统变量启用时,MySQL Server 才会设置DEFINER
用户授予的所有角色,包括强制角色。因此,默认情况下,不会检查通过角色授予的权限。对于存储程序,如果执行应该使用不同的角色,可以在程序体中执行SET ROLE
来激活所需的角色。这需要小心,因为授予的角色权限可以被更改。