存储程序(过程、函数、触发器和事件)和视图在使用前定义,并在引用时在确定其权限的安全上下文中执行。适用于存储对象执行的权限由其 DEFINER
属性和 SQL SECURITY
特性控制。
存储对象定义可以包含一个 DEFINER
属性,该属性指定一个 MySQL 帐户。如果定义省略了 DEFINER
属性,则默认的对象定义者是创建它的用户。
以下规则确定您可以为存储对象的 DEFINER
属性指定哪些帐户:
如果您拥有
SET_ANY_DEFINER
权限,则可以指定任何帐户作为DEFINER
属性。如果该帐户不存在,则会生成警告。此外,要将存储对象的DEFINER
属性设置为具有SYSTEM_USER
权限的帐户,您必须拥有SYSTEM_USER
权限。否则,唯一允许的帐户是您自己的帐户,可以按字面指定,也可以指定为
CURRENT_USER
或CURRENT_USER()
。您不能将定义者设置为任何其他帐户。
使用不存在的 DEFINER
帐户创建存储对象会创建孤立对象,这可能会产生负面影响;请参阅 孤立存储对象。
对于存储例程(过程和函数)和视图,对象定义可以包含一个 SQL SECURITY
特性,其值为 DEFINER
或 INVOKER
,以指定对象是在定义者还是调用者上下文中执行。如果定义省略了 SQL SECURITY
特性,则默认为定义者上下文。
触发器和事件没有 SQL SECURITY
特性,并且始终在定义者上下文中执行。服务器根据需要自动调用这些对象,因此没有调用用户。
定义者和调用者安全上下文之间的区别如下:
在定义者安全上下文中执行的存储对象使用其
DEFINER
属性命名的帐户的权限执行。这些权限可能与调用用户的权限完全不同。调用者必须具有引用该对象的适当权限(例如,EXECUTE
权限以调用存储过程,或SELECT
权限以从视图中选择数据),但在对象执行期间,调用者的权限将被忽略,只有DEFINER
帐户权限才有效。如果DEFINER
帐户权限很少,则该对象可以执行的操作也会相应地受到限制。如果DEFINER
帐户权限很高(例如管理帐户),则该对象可以执行强大的操作,而无论是谁调用它。在调用者安全上下文中执行的存储例程或视图只能执行调用者对其具有权限的操作。
DEFINER
属性对对象执行没有影响。
请考虑以下存储过程,它使用 SQL SECURITY DEFINER
声明,以便在定义者安全上下文中执行:
CREATE DEFINER = 'admin'@'localhost' PROCEDURE p1()
SQL SECURITY DEFINER
BEGIN
UPDATE t1 SET counter = counter + 1;
END;
任何对 p1
具有 EXECUTE
权限的用户都可以使用 CALL
语句调用它。但是,当 p1
执行时,它是在定义者安全上下文中执行的,因此使用其 DEFINER
属性命名的帐户 '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
的 EXECUTE
权限或对表 t1
的 UPDATE
权限,则 p2
将失败。
孤立存储对象是指其 DEFINER
属性指定的帐户不存在的存储对象:
在创建对象时指定一个不存在的
DEFINER
帐户,就会创建一个孤立存储对象。如果执行
DROP USER
语句删除了对象的DEFINER
帐户,或者RENAME USER
语句重命名了对象的DEFINER
帐户,则现有的存储对象可能会变成孤立对象。
孤立存储对象可能会出现以下问题:
由于
DEFINER
帐户不存在,因此如果对象在定义者安全上下文中执行,则可能无法按预期工作:对于存储例程,如果
SQL SECURITY
值为DEFINER
,但定义者帐户不存在,则在例程执行时会发生错误。对于触发器,在帐户实际存在之前,最好不要激活触发器。否则,有关权限检查的行为未定义。
对于事件,如果帐户不存在,则在事件执行时会发生错误。
对于视图,如果
SQL SECURITY
值为DEFINER
,但定义者帐户不存在,则在引用该视图时会发生错误。
如果随后为与该对象无关的目的重新创建了不存在的
DEFINER
帐户,则该对象可能会带来安全风险。在这种情况下,该帐户将““采用” 该对象,并且如果具有相应的权限,则能够执行该对象,即使这不是预期的行为。
服务器执行以下帐户管理安全检查,旨在防止(可能无意中)导致存储对象变成孤立对象或导致采用当前孤立的存储对象的操作:
如果要删除的任何帐户被指定为任何存储对象的
DEFINER
属性,则DROP USER
将失败并返回错误。(也就是说,如果删除某个帐户会导致存储对象变成孤立对象,则该语句将失败。)如果要重命名的任何帐户被指定为任何存储对象的
DEFINER
属性,则RENAME USER
将失败并返回错误。(也就是说,如果重命名某个帐户会导致存储对象变成孤立对象,则该语句将失败。)如果要创建的任何帐户被命名为任何存储对象的
DEFINER
属性,则CREATE USER
将失败并返回错误。(也就是说,如果创建帐户会导致该帐户采用当前孤立的存储对象,则该语句将失败。)
在某些情况下,即使帐户管理语句可能会失败,也可能需要特意执行这些语句。为此,如果用户拥有 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
帐户的存储对象。编写定义者上下文对象时应记住,它们可能能够访问调用用户没有权限的数据。在某些情况下,您可以通过不向未经授权的用户授予特定权限来阻止对这些对象的引用
但是,触发器和事件不存在此类控制,因为它们始终在定义者上下文中执行。服务器会根据需要自动调用这些对象,用户不会直接引用它们
触发器由对与其关联的表的访问激活,即使是沒有特殊权限的用户进行的普通表访问也是如此。
事件由服务器定期执行。
在这两种情况下,如果
DEFINER
帐户是高度特权的,则该对象可能能够执行敏感或危险的操作。如果创建对象所需的权限被从创建该用户的帐户中撤消,则这仍然适用。管理员在向用户授予对象创建权限时应格外小心。默认情况下,当执行具有
SQL SECURITY DEFINER
特性的例程时,MySQL 服务器不会为DEFINER
子句中命名的 MySQL 帐户设置任何活动角色,而只会设置默认角色。例外情况是如果启用了activate_all_roles_on_login
系统变量,在这种情况下,MySQL 服务器会设置授予DEFINER
用户的所有角色,包括强制性角色。因此,默认情况下,在发出CREATE PROCEDURE
或CREATE FUNCTION
语句时,不会检查通过角色授予的任何权限。对于存储程序,如果执行应使用与默认角色不同的角色进行,则程序体可以执行SET ROLE
来激活所需的角色。这必须谨慎行事,因为分配给角色的权限可能会更改。