23.6 存储的对象访问控制

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

DEFINER 属性

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

以下规则确定可以将哪些帐户指定为存储对象的DEFINER属性:

  • 如果您具有SUPER特权,则可以将任何帐户指定为DEFINER属性。如果该帐户不存在,则会生成警告。

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

使用不存在的DEFINER帐户创建存储的对象会创建一个孤立的对象,这可能会带来负面影响;参见孤立存储对象

SQL 安全特性

对于存储的例程(过程和函数)和视图,对象定义可以包括SQL SECURITY特性,其值是DEFINERINVOKER,以指定对象是在定义者还是调用者上下文中执行。如果定义忽略SQL SECURITYFeature,则默认为定义者上下文。

触发器和事件没有SQL SECURITYFeature,并且始终在定义程序上下文中执行。服务器根据需要自动调用这些对象,因此没有调用用户。

定义者和调用者安全上下文的区别如下:

  • 在定义者安全性上下文中执行的存储对象以其DEFINER属性命名的帐户的特权执行。这些特权可能与调用用户的特权完全不同。调用者必须具有适当的特权才能引用该对象(例如,EXECUTE调用存储过程或SELECT从视图中选择),但是在对象执行期间,调用者的特权将被忽略,只有DEFINER帐户特权才重要。如果DEFINER帐户具有很少的特权,则相应地限制对象可以执行的操作。如果DEFINER帐户具有很高的特权(例如 Management 帐户),则该对象可以执行强大的操作*,无论谁调用它.*

  • 在调用者安全上下文中执行的存储例程或视图只能执行调用者具有特权的操作。 DEFINER属性对对象执行没有影响。

Examples

考虑以下存储过程,该存储过程用SQL SECURITY DEFINER声明以在定义程序安全性上下文中执行:

CREATE DEFINER = 'admin'@'localhost' PROCEDURE p1()
SQL SECURITY DEFINER
BEGIN
  UPDATE t1 SET counter = counter + 1;
END;

具有p1EXECUTE特权的任何用户都可以使用CALL语句来调用它。但是,在执行p1时,它在定义程序安全性上下文中执行,因此以'admin'@'localhost'特权(称为DEFINER属性的帐户)执行。该帐户必须具有p1EXECUTE特权以及对象主体中引用的 tablet1UPDATE特权。否则,该过程将失败。

现在考虑此存储过程,该存储过程与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特权或 tablet1UPDATE特权,则p2失败。

孤立存储对象

一个孤立的存储对象是其DEFINER属性为其命名一个不存在的帐户的对象:

  • 可以通过在创建对象时指定一个不存在的DEFINER帐户来创建孤立存储的对象。

  • 通过执行删除对象DEFINER帐户的DROP USER语句或重命名对象DEFINER帐户的RENAME USER语句,可以将现有的存储对象孤立。

孤立存储的对象可能通过以下方式出现问题:

  • 因为DEFINER帐户不存在,所以如果它在定义者安全上下文中执行,则该对象可能无法按预期工作:

  • 对于存储的例程,如果SQL SECURITY值为DEFINER但定义器帐户不存在,则会在例程执行时发生错误。

    • 对于触发器,在帐户确实存在之前才进行触发器激活不是一个好主意。否则,关于特权检查的行为是不确定的。

    • 对于事件,如果该帐户不存在,则会在事件执行时发生错误。

    • 对于视图,如果SQL SECURITY值为DEFINER但定义器帐户不存在,则在引用该视图时会发生错误。

  • 如果随后出于与对象无关的目的而重新创建不存在的DEFINER帐户,则该对象可能会带来安全风险。在这种情况下,帐户“采用”该对象,并具有适当的特权,即使该对象不是故意的,也能够执行该对象。

要获取有关在 MySQL 安装中用作存储对象定义程序的帐户的信息,请查询INFORMATION_SCHEMA

此查询标识哪些INFORMATION_SCHEMAtable 描述具有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      |
+--------------------+------------+

结果告诉您要查询哪些 table 以发现存在哪些已存储对象DEFINER值以及哪些对象具有特定的DEFINER值:

  • 要确定每个 table 中存在哪些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';

对于ROUTINEStable,查询包括ROUTINE_TYPE列,以便输出行区分DEFINER是用于存储过程还是用于存储函数。

如果您要搜索的帐户不存在,则这些查询显示的任何对象都是孤立对象。

Risk-Minimization Guidelines

为了最大程度地减少创建和使用存储对象的潜在风险,请遵循以下准则:

  • 不要创建孤立的存储对象;也就是DEFINER属性为其命名不存在的帐户的对象。不要通过删除或重命名任何现有对象的DEFINER属性命名的帐户来使存储的对象变得孤立。

  • 对于存储的例程或视图,请尽可能在对象定义中使用SQL SECURITY INVOKER,以便只有具有适合于该对象执行的操作权限的用户才能使用它。

  • 如果在使用具有SUPER特权的帐户时创建定义者上下文存储的对象,请指定一个显式的DEFINER属性,该属性命名一个仅拥有该对象执行操作所需特权的帐户。仅在绝对必要时才指定特权较高的DEFINER帐户。

  • Management 员可以通过不授予用户SUPER特权来阻止用户创建指定高特权DEFINER帐户的存储对象。

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

  • 没有EXECUTE特权的用户不能引用存储的例程。

    • 没有适当权限的用户无法引用视图(SELECT从中选择,INSERT插入其中,依此类推)。

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

  • 触发器通过访问与其关联的 table 来激活,即使没有特殊特权的用户也可以通过普通 table 进行访问。

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

在这两种情况下,如果DEFINER帐户都具有很高的特权,则该对象可能能够执行敏感或危险的操作。如果从创建对象的用户帐户中撤消了创建对象所需的特权,这仍然适用。Management 员在授予用户对象创建特权时应格外小心。