存储程序(过程、函数、触发器和事件)和视图在使用之前定义,并在被引用时在安全上下文中执行,该上下文决定了它们的权限。适用于存储对象执行的权限由其 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
特性,并且始终在定义者上下文中执行。服务器会根据需要自动调用这些对象,因此没有调用用户。
定义者和调用者安全上下文区别如下
考虑以下存储过程,它使用 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'
的权限执行,该帐户被命名为其 DEFINER
属性。此帐户必须对 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
来激活所需的角色。执行此操作时必须谨慎,因为分配给角色的权限可能会更改。