MySQL 8.4 参考手册  /  存储对象  /  存储对象访问控制

27.6 存储对象访问控制

存储程序(过程、函数、触发器和事件)和视图在使用之前定义,并在被引用时在安全上下文中执行,该上下文决定了它们的权限。适用于存储对象执行的权限由其 DEFINER 属性和 SQL SECURITY 特性控制。

DEFINER 属性

存储对象定义可以包含一个 DEFINER 属性,该属性命名一个 MySQL 帐户。如果定义省略了 DEFINER 属性,则默认的对象定义者是创建它的用户。

以下规则决定了您可以为存储对象的 DEFINER 属性指定哪些帐户

  • 如果您拥有 SET_ANY_DEFINER 权限,则可以指定任何帐户作为 DEFINER 属性。如果该帐户不存在,则会生成警告。此外,要将存储对象的 DEFINER 属性设置为具有 SYSTEM_USER 权限的帐户,您必须拥有 SYSTEM_USER 权限。

  • 否则,唯一允许的帐户是您自己的帐户,可以按字面指定,也可以指定为 CURRENT_USERCURRENT_USER()。您不能将定义者设置为任何其他帐户。

使用不存在的 DEFINER 帐户创建存储对象会创建一个孤立对象,这可能会产生负面后果;请参阅 孤立存储对象

SQL SECURITY 特性

对于存储例程(过程和函数)和视图,对象定义可以包含一个 SQL SECURITY 特性,其值为 DEFINERINVOKER,以指定对象是在定义者还是调用者上下文中执行。如果定义省略了 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 执行时,它是在定义者安全上下文中执行的,因此使用 '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 属性值是什么。如果调用者缺少对 p2EXECUTE 权限或对表 t1UPDATE 权限,则 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 EVENTALTER 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 帐户的存储对象。

  • 编写定义者上下文对象时应记住,它们可能能够访问调用用户没有权限访问的数据。在某些情况下,您可以通过不授予未经授权的用户特定权限来阻止对这些对象的引用。

    • 没有 EXECUTE 权限的用户无法引用存储例程。

    • 没有相应权限的用户无法引用视图(SELECT 用于从中选择数据,INSERT 用于向其中插入数据,等等)。

    但是,触发器和事件不存在此类控制,因为它们始终在定义者上下文中执行。服务器会根据需要自动调用这些对象,并且用户不会直接引用它们。

    • 触发器由对其关联的表的访问激活,甚至是普通用户对其关联的表的访问,即使这些用户没有特殊权限。

    • 事件由服务器按计划执行。

    在这两种情况下,如果 DEFINER 帐户权限很高,则该对象可能能够执行敏感或危险的操作。如果创建该对象所需的权限被从创建该用户的帐户中撤销,则情况仍然如此。管理员在授予用户对象创建权限时应格外小心。

  • 默认情况下,当执行具有 SQL SECURITY DEFINER 特性的例程时,MySQL 服务器不会为 DEFINER 子句中命名的 MySQL 帐户设置任何活动角色,而只会设置默认角色。例外情况是启用了 activate_all_roles_on_login 系统变量,在这种情况下,MySQL 服务器会设置授予 DEFINER 用户的所有角色,包括强制性角色。因此,默认情况下,在发出 CREATE PROCEDURECREATE FUNCTION 语句时,不会检查通过角色授予的任何权限。对于存储程序,如果执行应使用与默认角色不同的角色进行,则程序体可以执行 SET ROLE 来激活所需的角色。执行此操作时必须谨慎,因为分配给角色的权限可能会更改。