InterSystems IRIS 提供广泛的可配置安全选项,但许多开发人员主要使用角色和资源来保护整个表或例程。今天,我们将深入探讨。我们也可以分别确保单个列和行的安全,但这两种机制的操作方式截然不同。让我们从列开始。
列安全
为便于测试和演示,我们将保持表结构简洁明了。我们在 USER 名称空间中有一个名为 "Person "的表,其中包含 ID 列、出生日期列 (DOB)、名和姓。
Class User.Person Extends %Persistent
{
Property FirstName As %String
Property LastName As %String
Property DOB As %Date
Property User As %String
}
我们将创建一个名为 limited_access 的角色,它将实现一些列的安全性,但我们必须记住的第一件事是确保将 %DB_User 资源添加到该角色,以便用户可以访问数据库。完成这些后,我们就可以开始考虑列的问题了。我们将进入角色设置中的 SQL 表选项卡,确保选择了 USER 命名空间。然后单击 "添加表(Add Tables)"按钮右侧的 "添加列(Add Columns)"按钮。这将打开我们控制列权限的对话框。

该界面与添加表权限的界面非常相似。值得注意的是 "删除(DELETE ) "选项不在其中,因为它不能在列级别进行控制。这是有道理的,因为我们无法删除表中记录的单个列。不过,我们可以保留对 SELECT、INSERT、UPDATE 和 REFERENCES 权限的控制。我们还可以使用 "授予管理员(Grant Admin)"复选框,允许具有该角色的用户将相同的权限委托给其他用户或角色。在今天的示例中,我们将授予该角色 ID、FirstName 和 LastName 列的所有权限。但是,我们将只为 DOB 列分配 SELECT 权限。完成这些更改后,我们将保存角色。这时你会发现,角色配置中该表的条目在权限列下显示了一个连字符 (-)。不过,它还包含一个 "编辑列(Edit Column )"选项。

如果我们点击右侧的 "编辑列(Edit Column)"链接,就可以查看为每一列单独定义的确切权限。

在这里,我们可以修改或撤销特定列的权限。如果选中 "添加列(Add Column) "复选框,添加列的初始配置表单将重新出现在下方,允许我们进一步添加。如果遇到角色中列出的表格似乎缺乏已定义的权限,则应单击 "编辑列(Edit Column)"链接,检查特定列的权限。
为了测试此配置,我们需要设置一个具有此角色的用户。我们将这个用户命名为 testuser。它将拥有我们的 limited_access 角色,我们还将授予它 %Developer 角色,以访问管理门户的 SQL 部分并执行一些查询。例如,请看下面的 SQL 查询。
INSERT INTO Person(DOB,FirstName,LastName) VALUES(%ODBCIN('1900-09-03'),'David','Hockenbroch')
如果我们以具有 %All 权限的超级用户身份登录并执行此查询,查询将会成功。但是,如果我们注销管理门户并以测试用户身份重新登录,则会出现错误。

查询失败的原因是用户没有权限插入 DOB 列。如果我们调整查询,省略受限列,查询就会成功。下面的查询对该用户仍然有效:
INSERT INTO Person(FirstName,LastName) VALUES('David','Hockenbroch')
同样,如果用户没有更新或选择列的权限,任何违反这些限制的查询都会失败。例如,如果我从列权限中完全删除了 DOB 列,就无法执行 SELECT * FROM Person 命令。相反,我必须使用 SELECT ID、FirstName、LastName FROM Person 命令。请注意,如果用户无法访问某些计算列,计算仍会正确执行。
如果我们稍微修改一下类的定义,就会引入一个全新的问题。假设我们决定修改类定义,在 DOB 属性中包含 Required 关键字。如果我们尝试运行上述查询,就会导致错误,而不是成功。

在建立列权限时,您必须牢记这一点:如果您拒绝给予用户某一列的 INSERT 权限,而该列又不是通过计算代码、初始表达式或类似方法自动分配的,那么该用户将根本无法插入任何记录!
行级安全
现在,我们将探讨行级安全性。我们的目标是确保只有创建行的用户才能访问该行。
行级安全性的功能与列级安全性截然不同。我们不能通过管理门户( Management Portal)配置它。相反,我们必须修改我们的类定义。首先,我们应该添加一个属性:
Property Creator As %String [ SqlComputeCode = {set {*} = $USERNAME}, SqlComputed, SqlComputeOnChange = %%INSERT]
这是一个 SQL 计算属性,在插入记录时会自动将其值设置为当前用户名。我们将为每个用户插入一条记录。然后,我们应该观察以下记录:

接下来,我们需要覆盖类定义中的 ROWLEVELSECURITY 参数。将其设置为 1 将自动引导 IRIS 将读者列表存储在名为 %READERLIST 的属性中。不过,在我们的例子中,由于我们创建了一个属性来存储有权访问该行的用户名,因此我们将用该列的名称(Creator)覆盖该参数。请记住,由于行级安全性可以通过角色或用户名来处理,因此如果需要,我们可以使用包含角色名称的列来代替。
如果此时我们试图从表中选择任何数据,查询将返回空值。出现这种情况是因为我们在现有表中添加了行级安全性,而行级安全性依赖于在用于安全性的字段上建立索引。现在我们必须重建表的索引。我们将通过管理门户完成这项工作。在 SQL 区域,我们将选择左侧的表,然后单击操作,最后单击重建表的索引。

现在有了适当的索引,当用户从表中选择时,他们将只看到自己的行。必须认识到,这一限制甚至适用于超级用户!在 IRIS 的大多数安全环境中,拥有 %All 角色的用户几乎可以访问所有内容,但行级别的安全是一个明显的例外。即使我们以超级用户身份运行 DELETE * FROM Person 查询,也只能删除超级用户有权访问的记录。
我们可以用另一种方法实现同样的结果。这次,我们将 ROWLEVELSECURITY 参数设置为 1,但在类中添加一个 %SecurityPolicy 方法。
ClassMethod %SecurityPolicy(Creator) As %String [ SqlProc ]
{
return Creator
}
该方法被定义为返回字符串并使用 SqlProc 关键字的类方法。它可以接受任意数量的参数,但这些参数必须是类本身的列名,而且参数的名称必须与列名精确匹配。在本例中,由于我们处理的列名为 "Creator",因此方法参数也必须名为 "Creator"。我们只需返回该列的值即可实现我们的目标。只要最终返回的是用户名或角色名,该方法就可以根据需要变得复杂。如有必要,它也可以返回以逗号分隔的名称列表。
当 ROWLEVELSECURITY 参数设置为 1 时,%READERLIST 属性将用于存储谁有权访问该行。如果希望在查询中查看该列表,可以执行类似下面的操作。
SELECT %READERLIST, * FROM Person
除表安全性外,还执行行级安全性。除非用户同时拥有表和行的权限,否则无法访问、更新或删除行。重要的是,行级安全仅在使用 SQL 时适用,而不是在使用对象访问或直接操作全局时。如果以绕过此安全层的方式访问数据,请务必谨慎!
认真使用这两个强大的工具,可以大大提高数据安全性!