阅读其他章节:
Power BI 学谦
终于,第五章来了。
本章介绍的是如何在PowerBI模型中实现各类安全性保障。除了我们所熟知的行级别安全性RLS,本文更是介绍了对象级别安全性、表级别安全性、列级别安全性、值级别安全性等。有待大家根据自己的实际业务场景,实现更加符合要求的安全性要求。
全文目前已经翻译完成,目前正在进行校稿完善阶段。
后续本公众号会按照节奏公布相关文章,感谢大家的支持。
对本章翻译内容的意见与建议,欢迎在评论区或微信群提交,谢谢。
在处理数据时,你可能会遇到一些数据需要对其进行加密处理。即使在组织内部,有些人的权限也会高于其他人。在 Power BI 模型中,也提供了一些可以保证数据的安全性的方法。在本章中,你将学习如何来实现。
请注意,在 Power BI 的报表和仪表板上进行分发或共享的安全性,我们将不做介绍。相反,我们将重点放在 Power BI 模型中的安全性上。常规的方案是,使用同一报表的两个用户根据其权限设置将看到不同的报表内容。
本章涵盖以下几个主题。
使用行级别安全性(Row-Level Security,RLS),可以限制用户查看 Power BI 模型中的数据。RLS 是 Power BI 模型中的主要安全形式。它之所以称为行级别,是因为你可以定义模型中每个表中哪些行对用户可见。值得注意的是,由于 RLS 的设置是对于整个模型起作用的,因此任何基于该模型的可视化报告都将满足其安全策略。
在深入探讨之前,让我们先明确一点:当你需要一个对模型进行权限设置时,建议你一定要使用RLS(或与此相关的对象级别安全性的相关概念,我们将在本章后面讨论),不要试图想办法绕过它,也不要试图通过共享报告(或者不共享)来实现。你无法确定你的报告将来会有何种用途:用户可能会获得对 Power BI 模型的访问权限,也可能意外被添加到安全组,或者其它无法预测的情况。同理,不要试图通过写一些在满足特定条件下返回特定数据的 DAX 度量值的方式来确保数据的安全。基于该模型开发报表的人员可以轻松绕过这些条件。简单来说就是:每个能够访问模型的用户只能看到那些被允许看到的数据。
RLS 是基于安全角色实现的。可以基于不同角色设置独立的安全策略。例如,设置高管的安全角色,或者人力资源经理的安全角色、甚至你还可以为单个销售人员设置安全角色。
安全角色是Power BI模型设计的一部分,而角色成员资格则不然。只有在发布模型后,才能将用户添加到角色。你可以根据需要拥有多个安全角色,但同时也要考虑一些注意事项,我们将在本节中介绍这些。
安全角色是通过“管理角色”窗口来定义和维护的,如图5.1所示。
图5.1 管理角色窗口
一旦你发布了定义过安全角色的模型后,除了已发布模型所在工作区的管理员、成员或参与者,其他人是无法访问的。
在 Power BI 服务中,可以通过数据集上下文菜单中的安全性选项来查看是否已定义了安全角色,如图5.2所示。
图5.2 查找安全性选项
我们可以将人员单独添加到安全角色,通过添加电子邮件地址或作为(安全)组的形式。
请注意,将某人添加到安全角色并不能保证他正常访问数据集,必须同时满足下面两个条件才可以。
创建安全角色后,就可以定义该角色的实际安全策略了。我们可以在模型中的一个或多个表上声明 DAX安全筛选器。请注意,DAX 安全筛选器通过角色和表来声明,我们可以在同一个表上具有不同的安全筛选器,只要它们具有不同的安全角色即可。
DAX 安全筛选器确定此安全角色中的用户将在表中看到哪些行。你可以将 DAX 安全筛选器理解为,在表中添加一列,然后判断每一行的值为“真”(TRUE)或“假”(FALSE)。最终只呈现那些判断条件后值为TRUE 的行,比如下面的代码。
Product[Category] = "Components"
可以理解为:在Product表上增加一列,判断表中每个产品的[Category]值是否为Components,如果是则返回返回TRUE,不是则返回FALSE。该筛选器添加到每一个要计算的度量值上,经过筛选后,表只返回那些类别为 Components 的结果。
我们不需要为每个表都设置安全筛选器,因为模型中的关系会将筛选器从一个表传播到另一个表。安全筛选器和度量值一样会考虑上下文。这意味着只需几个安全筛选器即可有效地保护模型。但请注意,模型中的更改可能会破坏安全策略!
图5.3中提供了一个简单示例,示例中含有两张表 (2),他们是一对多关系 (1)。使用安全角色SelectCanada后,它筛选加拿大(3)上的Country列(国家/地区列),fSales表将通该关系进行筛选。
图5.3 安全筛选器通过关系传播
当遇到具有双向交叉筛选的关系(在 PowerBI 中筛选方向是两个)时,在定义 RLS 时需要特别注意。双向关系的默认行为是仅向一个方向传播安全筛选器,那么如何确定这个方向呢?我们可以将交叉筛选器方向设置为“单一”,那么此时的方向就是安全筛选器的传播方向;或者如果关系是一对多,那么就是从一侧到多侧的方向。
你可以通过“编辑关系”窗口在两个方向上启用安全筛选器传播,如图5.4所示。
图5.4 编辑关系窗口
像Product[Category] = "Furniture"这样的静态安全筛选器在实际业务中其实并不常用。但是如果 RLS 是动态的,那么它会变得更加有用,这意味着安全筛选器可以根据访问者的身份来选择相应的权限。
如需下载请参考异步社区本书页面配套资源的“2.1 Row-level security.pbix”文件 |
---|
我们可以使用DAX函数来确定用户是谁,其中最推荐的是USERPRINCIPALNAME。此 DAX 函数返回用户的电子邮件地址,然后使用该地址给出正确的安全逻辑。
之前的 DAX 函数USERNAME在 Power BI Service中返回用户的电子邮件地址,但在 Power BI Desktop或 Analysis Services 实例中,它返回用户名。由于以这种方式查找用户的身份非常困难,因此引入了USERPRINCIPALNAME以使这更容易一些。使用 Power BI Embedded 时,可以在 Power BI 报表嵌入在其中的应用程序级别上配置安全性(也被称为“应用私有数据”)。在这种情况下,没有用户本身,而是在调用报表时,应用可以向 Power BI 提供标识符(密钥)。密钥可以是用户级标识符,但也可以是其他级别(如组织或部门)上的标识符。在这种情况下,USERPRINCIPALNAME将检索密钥,你可以将该密钥应用于安全筛选器中。 |
---|
不过,你通常不会在整个模型中使用电子邮件地址作为用户 ID,而是使用数字(HR 系统中的员工编号或生成的密钥)。无论哪种方式,你都需要一个单独的表,其中包含电子邮件地址和用户ID之间的映射。在一些简单模型中,你可以在此表(在本例中是UserSecurity表)与包含用户数据的表之间建立关系;或者,你甚至可以直接使用电子邮件地址来筛选Employee表。
图5.5 用户安全和员工表
在一些更大更复杂的模型中,仅保护Employee表可能还不够。在这种情况下,使用一个与模型中的任何表都不建立关系的UserSecurity表是一个比较好的选择。
如果不这样做,可能会导致从UserSecurity表到多个表的多个关系路径并由此产生一些非活动关系。
使用独立的UserSecurity表时,你需要从表中检索用户ID作为DAX安全筛选器的一部分。例如,为了保护Employee表,DAX 安全筛选器应设置为如下所示。
VAR ThisUser =
LOOKUPVALUE(
UserSecurity[EmpNr],
UserSecurity[Email],
USERPRINCIPALNAME()
)
RETURN
[EmpNr] = ThisUserRETURN
请注意,你可以在 DAX 安全筛选器中使用 DAX 变量。变量ThisUser从UserSecurity表中检索EmpNr值,使用USERPRINCIPALNAME()作为要查找的值。RETURN的后面,筛选器将检查Employee表当前行中的EmpNr值是否等于ThisUser变量,从而有效地筛选出适用于当前用户的行。
请记住,UserSecurity表包含敏感数据,你同事需要考虑是否需要保护此表本身!你可以在UserSecurity表上设置特定的安全筛选器,如下所示。
FALSE()
此筛选器将使表中的任何行对任何用户都不可见。仅当UserSecurity表与其他表无关时,这才有效,因为不应将此筛选器传递到模型的其余部分。
请记住,安全筛选器是同时应用的,因此不会相互依赖,就像CALCULATE函数中的筛选器参数一样。这意味着,当你使用上面的安全筛选器来隐藏UserSecurity表中的所有行时,仍然可以使用上述方法在另一个安全筛选器中检索当前用户。 |
---|
当然,RLS 的使用并不是一切顺利的,它同时会建模时对你限制一些操作。具体来说,你会发现非活动关系的某些用法失效了。
请看图5.6的示例。此模型包含一个事实表fHours,其中包含员工的工作小时数。员工的工作小时数通常我们指的是直接工时(Direct Hours)。但他们也把时间花在其他事情上的会议、休假、生病等,这些被称为间接工时(Indirect Hours)。每个项目都有一个项目经理,同时他也是该项目的一名员工。
图5.6 fHours表(工时表), Project表(项目表)和 Employee表(员工表)
如需下载请参考异步社区本书页面配套资源的“InactiveRelationship.pbix”文件 |
---|
在此示例模型中,直接工时数可以通过项目汇总,也可以通过项目经理汇总;当然也可以通过员工汇总间接工时。因此,Employee表与fHours表有两种关系,其中有一组为非活动关系。在本示例中,fHours和Employee两个表之间的关系被设置为非活动状态。
那么,如何计算此模型中的直接工时呢?基本公式其实很简单,代码如下。
Direct Hours = SUM(fHours[Hours])
由于fHours表中的间接工时没有对应的项目,因此会在Project表中添加一个空白行。这么做的目的是为了保证上面的公式在计算时,空白的项目会被呈现出来。当然,你可以通过一个稍微复杂的公式来测试这个结果,代码如下。
Direct Hours =
CALCULATE(
SUM(fHours[Hours]),
NOT(ISBLANK(fHours[ProjectNr]))
)
同样地,计算间接工时也用相似的办法,不过我们此时关心的是ProjectNr列中空白的行。我们需要激活fHours和Employee表之间的关系,使用如下代码。
Indirect Hours =
CALCULATE(
SUM(fHours[Hours]),
ISBLANK(fHours[ProjectNr]),
USERELATIONSHIP(fHours[EmpNr], Employee[EmpNr])
)
根据以上这些度量值,你可以按员工得出直接工时和间接工时(请注意,这里的直接工时是某员工作为项目经理的项目工时),如图5.7所示。
图5.7 按员工得出的直接和间接工时
现在,假设你要保护此模型。让我们创建一个简单的安全角色,该角色仅返回Adams, Doug的结果。我们需要在 Employee表上添加一个如下的 DAX 安全筛选器。
[FullName] = "Adams, Doug"
当此安全角色处于活动状态时,你将看到视觉对象直接返回了一个错误,如图5.8所示。
图5.8 无法加载此视觉错误消息的数据
这似乎是一个奇怪的错误消息。fHours表有两个到Employee表的关系,但USERELATIONSHIP使用的是非活动关系,对吧?当涉及到度量值的计值上下文时,这的确没问题,但对于安全筛选器则不是这样。
实际上,发生这个错误你应该感到庆幸,因为我们正在尝试做的是删除或更改fHours表上的安全性。正常情况是,我们只看到fHours中项目经理为 Doug 时所对应的行。而使用USERELATIONSHIP时,我们告诉模型忽略这些设置,并允许我们访问其他行。为了保证数据的安全,模型不允许你这样做。毕竟,能够执行此操作意味着那些具有自助服务能力的用户,也就是那些可以编写自定义度量值的用户,可能会覆盖原有的安全筛选器并危及模型的安全性。
此行为非常关键,这意味着安全性在模型设计时就应该加以关注,否则就很容易遇到麻烦。这里的示例并不是说应该始终避免非活动的关系,而是如果将来需要用到安全性时能识别出潜在问题。
没有单一的方法来解决这个问题;这完全取决于你的分析需求是什么。例如,你可以将事实表一分为二,其中一个事实表表示直接工时,另一个表示间接工时,就像图5.9所展示的那样。
图5.9 将事实表拆分为 fHoursDirect 和 fHoursIndirect
但是,此解决方案不允许通过员工(作为从事项目工作的人,而不是项目经理)得到直接工作时间。
另一种方案是复制Employee表的内容并将其命名为Project Manager表(项目经理表),如图5.10所示。
图5.10 添加项目经理表
此解决方案需要Project Manager和Employee表都设置 DAX 安全筛选器。
在设计安全策略之后,你肯定还需要测试这些策略,以查看他们是否按照预定的想法工作。幸运的是,Power BI Desktop 和 Power BI 服务都具有“通过以下角色查看”的功能,允许你查看特定角色中可见的数据。
使用“通过以下角色查看”选项,可以模拟特定用户登录并检查此用户能够看到的内容。在 Power BI Desktop 中,操作很简单:如图5.11所示,在“以角色身份查看”窗口中,选择要测试的角色,也可以使用“其他用户”选项。在框内你可以输入要测试的用户电子邮件地址。
图5.11 Power BI Desktop 中的“以角色身份查看”窗口
在 Power BI Service中,它的工作方式基本相同,但“其他用户”选项有些隐蔽。在数据集上选择“安全性”时,单击安全角色右侧的三个点,然后选择“以角色身份进行测试”,如图5.12所示。
图5.12 Power BI 服务中的“以角色身份测试”选项
当以角色查看报表时,再次单击该角色,在顶部蓝色的“当前查看时的身份为”那里,如图5.13所示,你可以选择输入要模拟的用户的电子邮件地址。
图5.13 选择要模拟的人
测试 RLS 时存在一个限制:如果已发布的 Power BI 模型采用了实时连接,那么你就无法轻松地对其进行测试,因为报表与模型不在同一文件中。这的确是一个问题,因为“实时连接”是部署报表的常用方法。在下一节中,我们将介绍一种使用实时连接测试 RLS 的方法,使测试人员能够轻松模拟任何用户。
在许多部署 Power BI 报表的组织中,技术支持人员会收到用户提出的问题。例如“我在报表中看不到任何数据”或者是“我应该看到 X 和 Y,但只能看到 X;哦,还有Z”。还有更加严重的情况,“John应该只看到X,但他却看到了全部的数据”。通常,这些问题是由于用户处于错误的角色(或无意中获取了对 Power BI 模型的编辑权限),要搞清这些问题我们可能需要重新认识安全策略。不管怎么说,能够模拟用户在报告中实际看到的内容还是很有用的。
需要提醒的一点是,你希望能够在生产版本(整个组织中实际使用的版本)上执行此操作,因此需要保证模拟本身必须是安全的。换句话说,这项操作应该仅限于特定用户(例如支持人员),而普通用户无法访问。此外,在生产环境中切换模拟角色应当很方便,这意味着它应该与正在使用的报表和对应的 Power BI 模型一起使用。
此问题的解决方案包含许多特定元素,如下。
让我们从头来做一遍。
我们从一个非常简单的模型开始。其中唯一的元素是查询参数,pImpersonation。
图5.14 在管理参数窗口中输入查询参数
图5.15 pImpersonation表
如需下载请参考异步社区本书页面配套资源的“2.1 Impersonation.pbix”文件 |
---|
在需要测试的原始 Power BI 模型中,现在可以通过以下步骤以 DirectQuery 的方式连接到pImpersonation表。该模型将成为一个复合模型。
图5.16 添加到模型中的pImpersonation 表
接下来,创建一个新的安全角色UserTest,该角色将检查pImpersonation 的值。如果它包含有效的电子邮件地址,则安全筛选器将采用该电子邮件地址来模拟用户。如果该值为空,则不应用任何安全筛选器。
例如,以下的代码是Employee表的适配安全筛选器。
VAR Impersonation =
SELECTEDVALUE(pImpersonation[pImpersonation])
VAR User =
LOOKUPVALUE(
UserSecurity[EmpNr],
UserSecurity[Email],
Impersonation
)
RETURN
ISBLANK(User) || [EmpNr] = User
第一个变量Impersonation 使用 SELECTEDVALUE检索pImpersonation参数(此模型中的列)的值。SELECTEDVALUE通常用于从列中检索值,当且仅当该列中只有一个唯一值;在本例中,总是只有一个值,因为在pImpersonation表中只有一行。
第二个变量User尝试使用LOOKUPVALUE从UserSecurity表中检索EmpNr值。请注意,UserSecurity用于将用户电子邮件地址转换为用户ID;如果找到EmpNr值,则安全筛选器的其余部分将使用该值。
请注意,当pImpersonation为空白值或电子邮件地址无效时,LOOKUPVALUE将返回BLANK。在这种情况下,我们不希望进行任何筛选。代码的最后一行ISBLANK(User))||[EmpNr] = User,意思是当变量User为空时,对于表中的每一行,ISBLANK(User)都为真。如果不是,则公式仅对列 EmpNr包含检索到的User值的行返回真值。
新的安全角色就位后,现在就可以发布模型了。不过,要使模拟角色起作用,你仍然需要处理以下几件事。
若要模拟用户并测试报表,PBITestUser 应登录到 Power BI,导航到 Impersonation 数据集,然后将数据集设置下的pImpersonation参数值更改为要模拟的电子邮件地址。不要忘记,更改参数值后需要刷新模拟数据集。由于该参数是模型中唯一的内容,因此刷新在一瞬间即可完成。接下来,找到与 PBITestUser 共享的报表并打开。
由于该报表的模型与Impersonation是采用DirectQuery连接方式,因此参数值更改后马上就可以用了。安全角色将选取电子邮件地址并相应地筛选报告。请注意,通常来说,在用户在登录会话期间首次连接到模型时,安全筛选器即应用。当你在查看报表时更改参数,它不会立刻选取新值。相反,请导航离开报告并等待一段时间再回来(在我们的测试中,大约10分钟就足够了)。
只要 PBITestUser 是 UserTest 角色中的唯一帐户,此方法就是安全的。由于没有其他角色使用pImpersonation值,因此Impersonation数据集甚至不需要去保护,毕竟它只是一个电子邮件地址。
若要以这种方式测试不同的安全角色,可以为模型上定义的每个安全角色创建一个特定的测试角色。这样,你甚至可以通过将 PBITestUser 添加到相应的测试角色来测试当用户是多个角色的成员时会发生什么情况。
我们使用了单独的测试工作区来托管Impersonation数据集,但你可能希望以其他方式解决此问题。重要的是,测试用户必须对此数据集具有读取访问权限,并且不得对要测试的模型具有完全访问权限(例如,成为模型所在工作区的管理员)。这是因为作为工作区管理员、成员或参与者,安全角色不会被应用。 |
---|
到目前为止,我们已经讨论了基于用户身份保护数据的可能性和一些陷阱。以上的讨论假设数据以某种方式直接与用户相关。在许多情况下,这是远远不够的,因为用户是更大的具有特定结构的组织的一部分。下一节将介绍这些情况。
在大多数组织中,数据并不直接与有权访问数据的单个用户相关。相反,有一群人每个人都可以访问不同的数据集。例如,经理可以访问向他们报告的员工的数据。DAX 包含一组函数来处理父子层次结构,如下所示:PATH函数。
首先,让我们看一个典型的组织结构,在本例中,我们的示例公司QuantoBikes的组织结构。QuantoBikes被组织成几个与大洲一致的部门,每个部门由多个团队组成。
组织结构图如图5.17所示。
图5.17 QuantoBikes 组织结构图
如图5.18所示,在 Employee 表中,此组织层次结构通过具有名为 MngrNr 或经理编号的列进行识别。此列包含每个员工的直接经理的员工编号;只有CEO这个职位没有对应的经理。
图5.18 QuantoBikes 的员工表
Power BI 假定一个员工没有多个直接经理,这是一个合理的假设。有些时候你可能需要处理具有多个父级的层次结构(例如,家谱):这些情况过于复杂,无法仅用PATH函数解决。我们不会在本书中介绍这些内容。当表中的多行在父列中包含空白值时,层次结构可以由多个树组成。 |
---|
如果我们设计一张表,表中对于父子层次结构重新编排,则可以得到一张包含所有信息的表。在我们的示例中,指的是从员工到经理,再到经理的经理,一直到层次结构的顶部。DAX 包含一系列可为你执行此操作函数,并提供有关层次结构的有用信息。
PATH(Employee[EmpNr],Employee[MngrNr])必须在 Employee 表的行上下文中进行计算。它将层次结构的两列作为参数,并返回从层次结构顶部到当前 EmpNr 的路径。结果是一个文本字符串,其中包含由竖线字符分隔的所有 EmpNr 值的串联。例如,Leo Johnson(员工 10106)的路径如下所示。
10001|10010|10101|10106
请注意,路径始终是文本,即使层次结构列是数字时也是如此。
函数 PATHCONTAINS 将路径和值作为参数,当值包含在路径中时返回TRUE。再次以Leo Johnson为例,以下代码的结果为TRUE。
PATHCONTAINS(
<Leo's path>,
10010
)
函数 PATHLENGTH 返回路径中的项数。换句话说,它返回层次结构中当前项显示的级别。例如,以下代码将返回值 4。
PATHLENGTH(<Leo's path>)
函数PATHITEM采用路径和数字N作为参数,返回层次结构从头计数(或层次结构的顶部)的第 N 项。例如,以下代码会返回10101。
PATHITEM(<Leo's path>, 3)
5.PATHITEMREVERSE
函数 PATHITEMREVERSE 与 PATHITEM 函数类似,但从路径的末尾(层次结构的底部)开始计数。
PATHITEMREVERSE(<Leo's path>, 3)
以上返回10010,这是Leo跨职级经理的编码。
同样,请注意,PATHITEM和PATHITEMREVERSE返回文本值,即使路径是从数值创建的也是如此。
PATH 函数的正常用法是使用 PATH 创建路径,并将其用作其他函数的输入。但是,你可以按照自己喜欢的任何方式创建路径字符串。PATH函数对路径的方式没有任何隐藏的知识被创造;他们只处理文本字符串并搜索管道字符。 |
---|
当数据具有分层结构时,可以使用PATH函数实现更复杂的安全逻辑。假设你想要经理有权直接或间接获取下属所有员工的数据安全策略。首先需要在Employee表中创建一个列,其中包含每个员工的层次结构路径,如下。
Path = PATH([empNr], [MngrNr])
Employee表上的 DAX 安全筛选器将再次检索已登录用户的员工编号,代码如下。
VAR ThisUser =
LOOKUPVALUE(
serSecurity[EmpNr],
UserSecurity[Email],
USERPRINCIPALNAME()
)
接下来是检查当前用户是否在员工的路径中。
RETURN
PATHCONTAINS([Path], ThisUser)
当登录用户位于员工的路径中时,PATHCONTAINS函数返回TRUE,否则返回 FALSE。因此安全筛选器的结果是,用户下层次结构中的所有员工都可见,而其他员工是不可见的。
通过巧妙地使用PATH函数,你可以实现各种高级安全规则。接下来我们看一个例子,我们创建一个策略:经理可以查看向他汇报的所有员工以及向他同级汇报的所有员工的数据。以下详细说明。
我们将需要相当多的代码来实现此策略,并会使用 DAX 变量来记录程序执行的过程。该代码涵盖以下步骤。
首先,让我们看看如何确定 John 是否为经理。这并不简单,因为汇报路径只能向上,而我们又没有可用的组织架构。但是,我们可以遍历 Employee 表并计算 John 在路径中出现的次数。不是管理者的人只会出现在他们自己的向上汇报路径上,而不会出现在其他人的路径上。要使用PATH函数,我们首先需要从UserSecurity表中检索 John 的员工编号。下面是 DAX 代码。
VAR ThisUser=
LOOKUPVALUE(
UserSecurity[EmpNr],
UserSecurity[Email],
USERPRINCIPALNAME()
)
VAR IsManager=
IF(
COUNTROWS(
FILTER(Employee,
PATHCONTAINS(Employee[Path],ThisUser)
)
)>1,
TRUE(),
FALSE()
)
当用户是管理员时,变量IsManager为TRUE。我们可以继续寻找向 John 汇报工作的人,但根据我们的策略,John 可以查看直接或间接向其经理汇报工作的员工数据。因此,从查看John的经理是否在员工的路径上是有意义的。我们使用LOOKUPVALUE检索已登录用户的MngrNr值(变量ThisUser),然后使用PATHCONTAINS检查John的经理是否出现在员工的路径中,代码如下。
VAR ThisUserMngr=
LOOKUPVALUE(
Employee[MngrNr],
Employee[EmpNr],
ThisUser
)
VAR ReportsToManager=PATHCONTAINS([Path],ThisUserMngr)
如果我们使用变量ReportsToManager,我们会把过多的员工选上。我们应该跳过John的经理和John的同级。要做到这一点,需要John和他的经理的职级;只有比 John 的经理低至少两级的员工才能被 John 查看。你可以使用PATHLENGTH函数来执行此操作,代码如下。
VAR ThisUserPath=
LOOKUPVALUE(
Employee[Path],
Employee[EmpNr],
ThisUser
)
VAR MngrLevel=PATHLENGTH(ThisUserPath)–1
请注意,安全筛选器是以Employee表作为迭代对象的,在每一行计算时我们都希望得到 John 经理的级别。有了这些信息,我们就可以推断出哪些员工应该是可见的,代码如下。
VAR ShouldBeVisible=
ReportsToManager
&& PATHLENGTH([Path]) >= MngrLevel+2
但是此处需要留意一下:有一个员工的ShouldBeVisible值返回FALSE,而此员工实际上应该可见。那就是John本人。因此,我们需要对以上变量做一下微调,代码如下。
VAR ShouldBeVisible=
(ReportsToManager
&& PATHLENGTH([Path]) >= MngrLevel+2)
||[EmpNr]=ThisUser
现在,我们可以将所有内容整合在一起,完成安全筛选器的设置,代码如下。
RETURN
IF(
IsManager,
ShouldBeVisible,
[EmpNr]=ThisUser
)
还剩下一个比较重要的会出错的情况:首席执行官,因为他没有上级经理,导致的结果是他只能看到自己。这是因为ThisUserMngr变量为BLANK,因此没有员工被标识为向她报告;毕竟,不包含任何路径就表示空白值,因此变量ReportsToManager对于任何员工都是FALSE。为此,我们需要新增一个变量来检测这种情况:当用户没有上级时,MngrLevel为零。因此,最终的筛选器应是如下所示。
RETURN
SWITCH(TRUE(),
MngrLevel=0,TRUE(),
IsManager,ShouldBeVisible,
[EmpNr]=ThisUser
)
这里的SWITCH语句首先检查用户是否为首席执行官,如果是,则可以查看所有员工;然后,它会检查用户是否为经理,如果是,就应用经理的安全规则。如果用户既不是首席执行官也不是经理,则只允许用户看到自己。
既然你已充分了解行级别安全性的实际应用,我们将讨论如何使用 RLS 实现更复杂的安全策略:保护属性,以及本章后面会介绍的聚合级别保护。
在本节中,我们将以完全不同的方式介绍 Power BI 模型中的安全性。在前面的部分中,我们重点介绍了限制模型表中对“行”是否可见的方法,这是最常见的安全需求。但实际业务场景中也会有其他形式的安全需求。如果你将行级别安全性视为“横向”安全性,那么考虑“垂直”安全性的也是有意义的。换句话说,我们是否可以保护列或属性?
只有当使用 Power BI 模型的人员群体较大时,才真正需要使用 RLS 保护该模型。如果你的模型仅由高管使用,则可能根本不需要 RLS。因为每个用户都可以查看所有数据。只有当受众群体变大时,才需要根据地理位置、客户细分,或如前面的章节中所述的根据组织结构对数据进行细分管理。
同样,如果你的模型仅适用于特定的业务流程(如销售和机会管理),则不需要保护特定属性。例如,该模型可能包含销售人员的姓名及其职责,但不包含他们的工资级别、出生日期或社保号码。当有关不同业务流程的数据组合到一个模型中时,如销售和人力资源管理数据,你需要包含不与用户共享的其他属性。
保护属性的一种方法是对象级别安全性(Object-level security),有如下的两种形式。
我们有意识地在这里使用“消失”这个词。例如,当使用表级别安全性保护Product表时,模型的行为就好像根本没有Product表一样。当使用列级别安全性保护列时,也会产生类似的效果。
我们不在这里详细展开介绍对象级别安全性的方方面面,部分原因是它是在不使用 DAX 的情况下实现的。本书重点想介绍的是稍后会提到的方法。当然,使用对象级别安全性也是有特定案例的。官方文档指出,它限制了对“敏感”表名和列名的访问,有些时候,你可能会有这么一些数据,它们敏感到连表名和列名都需要被限制在特定的几个人知道,更何况其中的内容了。(为什么此时我想到了一个表名:UFO类型,哈哈,但我觉得肯定有更严肃的例子)。
但是,表和列的消失会给 Power BI 模型和报表带来新的问题。当普通表与受保护的表建立关系时,你可能因为权限问题无法访问。更重要的是,当引用受保护的列或受保护的表中的列时,Power BI报表因为无法访问这些列或表而引发错误。
换言之,使用对象级别安全性会强制你将报表划分为对具有安全对象访问权限的用户版本,以及没有访问权限的用户版本。
有了这个,你可能会想,对于受保护的数据,使用一个单独的模型不是更好吗?毕竟,它只会被一个自定义报表使用。
尽管 Power BI 模型支持 OLS,但目前无法使用 Power BI Desktop 对其进行配置。 |
---|
现在,我们引入一种略有不同的保护属性的方法,它可以在一张报表中实现允许访问的用户和不允许访问的用户同时使用。我们创造了术语值级别安全性(value-level security,VLS),因为它是行级别和列级别安全性的混合体。使用值级别安全性,可以授予用户访问某些行中列的值的权限,但不能访问其他行中的列值。
通过使用 VLS,你可以实施安全策略,例如:经理可以看到向他们报告的员工工资级别,但看不到向同级报告的员工工资级别,哪怕他们可以看到这些员工及其销售数字。而另一个完全不同的例子中情况可能是这样的:教师可以看到学生的姓名,数字和成绩,但只有班主任才能看到学生的地址。作为某些学生班主任的教师可以看到他们的地址,但看不到其他学生的地址。
你可能希望使用 DAX 度量值来实现此目的。但从长远来看,这不是一个安全的解决方案。如果用户获得了 Power BI 模型的设计权限,他们可以创建自己的报表 (这是使用 Power BI 的过程中可能经常遇到的情况), 还可以创建自己的 DAX 度量值。这样,他们就可以随意访问模型中不受保护的所有内容。这意味着可以通过创建度量来绕过以度量值的方式实现的任何安全性。
更重要的是,一个严肃的模型可以包含数十个或数百个度量值。当每个度量都需要配备逻辑来实现属性安全性,你犯错的概率就会陡然增大,维护成本也会居高不下。相反,我们希望有一个解决方案,该方案可以适用于模型的任何度量值。
实现 VLS 需要建模和 DAX 安全筛选器的复杂组合。在下面的部分中,我们将重点介绍这些内容。
如需下载请参考异步社区本书页面配套资源的“2.1 Value-levelsecurity.pbix”文件 |
---|
设计 VLS 解决方案时,首先要知道一个安全的报表是什么样子的。我们不希望有任何的错误信息,因此图5.19中的视觉对象是最佳选择。在此示例中,部门员工的SSN 正确显示,其他员工显示为空。
图5.19 受 VLS 保护的报告
这里需要特别注意一点是,受保护的值在报告中是不显示的。但在此示例中,由于列 SSN是标签而不是度量值的结果,因此模型中必须有一个值才能在视觉对象中显示。这可以是空文本、BLANK值或其他值,并且该值必须真实存在于表的行中。
现在,如果你意识到对于某些用户,这些值应该是可见的,而对于其他用户来说,则要将保护的表(在本例中为Employee)拆分为两部分:一部分用于可公开访问的列(当然,受 RLS 限制的约束),另一部分包含私有列,如图5.20所示。
图5.20 将 Employee 表拆分为公共部分和私有部分
我们仍然需要将对应的行相互链接,因为我们在两个表中都有EmpNr列。你可以在两个表之间创建关系,这种关系甚至是一对一的。但这对我们没有帮助,它肯定不会为私有列提供空白值;相反,它把我们重新回到只有一张表的情况。
解决方案是向私有表中添加行。对于Employee表, Employee (private)表包含的行数必须是Employee表的两倍。我们将其分为两组,一组行包含EmpNr的所有值,以及所有私有数据,我们将这些行称为正行。
另一组行还包含EmpNr的所有值,但在私有列中是空白值(或你选择的任何其他显示方式),我们将这些行称为负行。附加列 Private 有助于区分正行和负行。图5.21示意性地显示了这一点。
图5.21 向Employee (private)表添加行
Employee (private)表通过多对一关系链接到Employee表,其中Employee (private)处于关系的“多”端。该关系需要启用双向交叉筛选。例如,通过FullName选择员工时,你希望选择该员工的私人数据。反之亦然:在选择薪酬水平时,你希望选择具有该薪酬水平的所有员工。因此,筛选器需要从员工传播到 Employee (private),反之亦然。
可以使用 M 脚本轻松创建中间表(此脚本假定你有一个sEmployee查询,该查询为 Employee 表提供基本数据,并且该查询本身未加载到模型中)。
letSource = sEmployee,PositiveRows = Table.SelectColumns(Source,{"EmpNr", "SSN", "DateOfBirth", "PayLevel"}),AddPrivate = Table.AddColumn(PositiveRows,"Private", each 1, Int64.Type),NegativeRows = Table.SelectColumns(Source,{"EmpNr"}),AddPrivate2 = Table.AddColumn(NegativeRows,"Private", each 0, Int64.Type),Combine = Table.Combine({AddPrivate, AddPrivate2})inCombine
此脚本创建sEmployee 表的两个副本,一个包含私有列,另一个仅包含EmpNr列。Private 列将分别添加到值为 1 或 0 的两个副本中。最后,对两个副本进行追加查询。
添加其他列可能很有用。例如,如果要在私有属性的安全策略中使用组织层次结构,则在两个副本中也包含MngrNr列是有意义的。
确保不要在关系上启用“在两个方向上应用安全筛选器”设置。正如你将在下一节中看到的那样,我们将使用Employee(private)上的安全筛选器来保护私有属性。这是对 Employee表的安全筛选器的补充。在这种情况下,该模型不允许针对默认方向相反的方向传播安全筛选器。
当你使用Employee 和 Employee(private)表中的列创建一些输出时,你会注意到每个员工的输出有两个副本:一个具有实际的私有属性(正副本),另一个具有空白私有属性(负副本)。产生这种情况的原因是我们设计Employee(private)表的方式,如图5.22所示。
图5.22 查看每个员工的两个输出行
但这意味着你现在可以使用行级别安全性来选择要显示的副本。对于其私有属性应可见的每个员工,请确保Employee(private)中的相应正值行可见,同时隐藏负值行。对于不应显示其私有属性的每个员工,使用非空白私有属性隐藏Employee(private)中的正值行,并使负值行可见。
Employee(private)上安全筛选器的 DAX 公式如下所示。
(
<when to show>
&& [Private] = 1
)
|| (
NOT(<when to show>)
&& [Private] = 0
)
我们将第一个<when to show>子句称为正子句,另一个子句称为负子句。例如,若要仅显示员工 10203 的私有属性,筛选器将为如下。
(
[EmpNr]=10203
&&[Private]=1
)
||(
[EmpNr]<>10203
&&[Private]=0
)
此表达式适用于EmpNr = 10203的单行和私有数据,以及具有其他值(用于 EmpNr
和空白值)的行。对于Employee(private)中的所有其他行,表达式为 false,并且这些行处于隐藏状态。
如你所见,对于每个员工,Employee(private)中只有一行可见。可以通过设置安全性来隐藏根本不应该可见的员工像往常一样在Employee表上进行筛选。如果在Employee上设置安全筛选器 [MngrNr] = 10201,则会得到如图5.23所示的结果(Faustina Bailey 是员工 10203)。
图5.23 查看一个员工的 SSN
在私有表的安全筛选器中,你可以像往常一样在 DAX 中应用所有可能的内容。例如,要这个实现安全策略:经理可以看到其直接下属的私有属性,但不能看到间接下属的私有属性,需要首先在Employee(private)表中有 MngrNr 和 Path 这两列。表的安全筛选器中的第一步是检索用户,代码如下。
VAR ThisUser =
LOOKUPVALUE(
UserSecurity[EmpNr],
UserSecurity[Email],
USERPRINCIPALNAME()
)
接下来,确定用户的路径和组织级别,代码如下。
VAR ThisUserPath =
LOOKUPVALUE(
'Employee (private)'[Path],
'Employee (private)'[EmpNr],
ThisUser
)
VAR ThisUserLevel = PATHLENGTH(ThisUserPath)
“只有直接报表”这个限制内容的意思是:为向此用户报告的员工(用户出现在其路径中),并且还得是直接下属(其级别比报告使用者级别低一级)。实现这一要求的 DAX 公式如下。
PATHCONTAINS([Path], ThisUser)
&& PATHLENGTH([Path]) <= ThisUserLevel + 1
请注意,此处使用“<=” (而不是只有“=”),目的是为了包含ThisUser,ThisUser是她自己路径中唯一与她级别相同的员工(根据定义,ThisUser不属于级别小于或级别高于ThisUserLevel的任何员工的路径,因此不包括更高的经理)。因此,对Employee(private)的筛选将是如下所示。
RETURN
(
PATHCONTAINS([Path], ThisUser)
&& PATHLENGTH([Path]) <= ThisUserLevel + 1
&& [Private] = 1
)
||
(
NOT(
PATHCONTAINS([Path], ThisUser)
&& PATHLENGTH([Path]) <= ThisUserLevel + 1
)
&& [Private] = 0
)
不要忘记在 PATHCONTAINS 子句和 PATHLENGTH 子句上使用 NOT 函数,特别要注意括号!
到目前为止,我们假设有一组员工属性是私有的,并且可能看到某些员工的私有属性的用户可以看到其所有私有属性。你可以更进一步,定义多组私有属性,例如,将公共属性与联系人详细信息分开,以供同级使用,并与只能由经理查看的 HR 属性分开。但是,要小心:这么多复杂的逻辑与操作,很快就会使模型变得异常复杂,从而难以管理,当然。这些操作也会对模型的性能产生影响。
但如果需要,你可以复制此结构以容纳多组私有属性。可以创建一个模型,如图5.24所示。
图5.24 设置多组私有属性
两个私有表都直接链接到公用表。对此设置后,你可以通过将 RLS 筛选器应用于各自的表来单独保护这两组私有属性。同样,公共表上的 RLS 确定哪些行完全可见。
最大的问题是针对启用了 VLS 的模型开发报表会变得混乱。正如我们在上一节中所看到的,当你对模型具有完全访问权限时,每当在报表中使用私有属性时,都会对行进行复制。
在模型本身中工作时,一种简单的方法是取消加载私有表的负行。这样,你将看到所有私有属性,至少你不会看到重复项。执行此操作的最佳方法是使用参数来设置是否加载负行。这样,你可以通过更改参数值一次切换所有的私有表。
另一种方法是创建一个Development表,其中包含值为0和1的 Private列。然后,可以创建从所有专用表到 Development 表的 Private 列之间的关系。这允许你在报表中设置筛选器(Development[Private] = 1)以关闭所有负行。这样,你可以轻松地从报表中切换,而不必公开私有表本身,如图5.25所示。
图5.25 创建与私有表有关系的开发表
使用这些方法,你将能够像用户一样在查看数据的同时生成报表。你仍需要确保用户看不到所有数据,因为他们对 Power BI 模型具有编辑权限。
行级别安全性的另一个应用是保护聚合级别,下一节将对此进行介绍。你可以使用类似的方法来保护属性,但同样有一些注意事项。
Power BI 模型安全的另一个条件与聚合级别相关。你可能会有这样的需求:“工资成本可以按团队查看,但单个员工的工资只能由他们的直接经理查看”。在本节中,我们将探讨确保在不同聚合级别上查看结果的方案。
我们在本章前面已经提到过:在度量值中通过 DAX 实现安全性是不安全的。在设计模型时,应始终考虑自助服务用户可能的需求,用户将能够针对模型编写自己的度量值。这样,你不必在度量值安全上面再花费功夫。
相反,安全性必须仅依赖于模型结构和 RLS。这意味着并不是你能想到的每个安全策略都可以实现。例如,你的用户可以要求按个人查看销售信息,但只能按团队查看销售利润。由于这两个度量值的计算都来自同一事实表的数据,因此无法满足此需求。在其他情况下,数据取自不同的事实表(例如,一个是按个人计算的销售额,一个是按团队计算的工资成本)。
确保工资只能按团队查看而不是按员工查看,最安全方法是不在员工级别加载这些数据。你可以创建一个工资事实表,其中包含每个团队的数据。这里明显的问题是,如何让授权用户在员工级别获得工资数据。可以使用其他数据集来执行此操作。
Power BI 较少使用的功能之一是跨报表钻取。这可不是一项能够容易实现的功能,因为它在很大程度上取决于模型和报表在 Power BI 服务中的发布方式。我们在此处就不详细介绍跨报表钻取了,但是要说明一点其功能,当你启用跨报表并且报表位于同一工作区中时,可以在报表中启用钻取操作,这些操作不只是可以跳转到同一报表中的另一页,还可以跳转到另一个报表中的某一页。
若要使跨报表钻取正常工作,只需要确保用于钻取操作的两个报表中的字段具有相同的名称,以便 Power BI 可以将它们识别为同一个字段。有趣的是,这些报表不必使用相同的底层模型。这意味着你可以创建一个包含按团队划分的工资成本的报表,并对显示特定团队里按员工的工资成本的详细报表进行钻取。详细报表的底层模型可以实现自己的安全策略,因此可以阻止未经授权的用户查看详细数据。
复合模型是混合了 DirectQuery 事实表和导入的事实表的 Power BI 模型。导入的表可以是DirectQuery 表的聚合版本。此功能旨在能够报告和分析数十亿行数据,并且基于(合理的)假设,即用户很少需要查看其数据中的较低详细级别。根据所问的问题,模型将选择从聚合表中检索结果,或者在需要时从 DirectQuery 表中检索结果。根据请求的聚合级别自动进行选择。为了确保聚合级别,这不是我们想要的;相反,我们希望基于安全规则进行选择。
幸运的是,这是可以做到的。你可以有一个事实表,比如fSalaryTeam,其中包含团队级别的工资成本,另一个数据表包含员工级别的工资成本,fSalaryEmployee。使用 RLS,你可以保护fSalaryEmployee避免未经授权的用户的访问,甚至可以仅允许用户访问fSalaryEmployee的部分数据,例如与他们自己的团队相关的部分数据。
在本节的其余部分中,我们将使用一个安全角色,该角色可以访问Teams表中的欧洲部门,以及fSalaryEmployee表中的Europe 2团队(TeamNr = 9),作为一个简单的示例,如图5.26所示。
图5.26 fSalaryTeam、fSalaryEmployee、Team 和 Employee 表
安全筛选器包括如下代码。
[Division] = "Europe" // in table Teams
RELATED(Employee[TeamNr]) = 9 // in table fSalaryEmployee
如需下载请参考异步社区本书页面配套资源的“2.1 Aggregation security1.pbix”文件 |
---|
这里的挑战是,你需要更改度量值的 DAX 代码;不是为了实现安全性本身,而是从一个事实表无缝切换到另一个事实表。理想情况下,你需要一个度量值,在团队级别或更高级别上进行计算时,从fSalaryTeam获取工资成本,但在详细级别上,计算在fSalaryEmployee表中进行。
此挑战归结为确定度量值的计值上下文到底是什么。在第4章 上下文和筛选中,你已经看到了几个对此有帮助的 DAX 函数(ISFILTERED、ISCROSSFILTERED等)。使用这些函数,可以构建一个在不同事实表之间切换的度量值。然而,真正操作起来这并不容易,并且会发生一些意外情况。你的第一次尝试可能是按照如下代码。
Salary Costs =
IF(
HASONEVALUE(Employee[EmpNr]),
SUM(fSalaryEmployee[Salary]),
SUM(fSalaryTeam[Salary])
)
这看起来没问题:每当选择一个员工编号时,无论以何种方式,从fSalaryEmployee表中计算工资,或者从fSalaryTeam表中计算。HASONEVALUE函数通常被那些没有太多经验的 DAX 开发人员使用,主要用于以下构造。
IF(HASONEVALUE(Table[Number]),
VALUES(Table[Number]) * 5
)
我们当然有更好的方法从列中提取单个值,不过除此之外,HASONEVALUE(以及类似地,HASONEFILTER)函数还有一个经常被忽视的属性:当在列中只选择一个值时,它们返回真(true)。当该数字大于 1 时,它们返回假(false),但当它为0时,它们也会返回假!
图5.27显示了我们的示例安全角色中薪资成本度量的输出。
图5.27 薪金成本输出
请注意,有多少员工似乎具有相同的工资成本,并且它们实际上等于团队的工资成本。还要注意,像Kai Bell这样的员工似乎属于两个团队!事实上,我们看到所有不属于团队的员工都以整个团队的结果出现。其原因是在这些情况下,HASONEVALUE返回0,导致度量值选择fSalaryTeam进行计算。有关评估这些员工的原因的深入讨论,请参见第8章 自动匹配。
现在,你可以尝试在 DAX 公式中使用ISFILTERED而不是HASONEVALUE。但是,按工资水平等方式报告工资成本无济于事。最好确定所选内容是否是团队的子集,在这种情况下,请切换到员工级别的数据。
一种方法是简单地计算员工数量,并将该数字与团队中的员工总数进行比较,代码如下。
Salary Costs =
IF(
COUNTROWS(Employee) =
CALCULATE(
COUNTROWS(Employee),
ALL(Employee),
VALUES(Team[TeamNr])
),
SUM(fSalaryTeam[Salary]),
SUM(fSalaryEmployee[Salary])
)
图5.28显示了此度量值的示例输出,包括员工姓名和性别(这两个值都是Employee表中的列)。
图5.28 Europe 2的薪资成本,按雇员姓名和性别分列
安全角色仅显示Europe 2的员工级别的欧洲部门和薪资成本。从输出中可以看出,欧洲境内所有团队的工资成本都返回了。这清楚地表明,这些结果取自fSalaryTeam表。个人员工的工资成本仅返回Europe 2;对于其他团队,度量值会从fSalaryEmployee表中正确检索数据,但什么也不会收到,因为安全筛选器起到了作用。在按性别报告薪酬成本时,我们不会考虑员工个人。不过,该度量值会识别出请求低于团队级别的聚合级别,并从fSalaryEmployee表中获取结果。
可以使用 RLS 将保护聚合级别与保护私有数据相结合,但执行此操作时需要注意一些其他事项。扩展模型将如图5.29所示。
图5.29 使用 Employee(private)表进行扩展
如果要将输出更改为按团队和薪资级别划分的薪资成本(Salary Costs),你会看到,对于每个薪资级别,你将获得每个团队的薪资成本。当然,原因是我们确定一个完整的团队是否在上下文中的方法现在使用另一个表而变得不尽如人意。应向公式中添加一个附加的ALL子句,代码如下。
Salary Costs =
IF(
COUNTROWS(Employee) =
CALCULATE(
COUNTROWS(Employee),
ALL(Employee),
ALL('Employee (private)'),
VALUES(Team[TeamNr])
),
SUM(fSalaryTeam[Salary]),
SUM(fSalaryEmployee[Salary])
)
这样,度量值就可以返回正确的结果,如图5.30所示。
图5.30 按团队和薪级分列的薪资成本
永远记住,私有表中的一半行必须隐藏以避免重复输出(对于图5.30来说,应用了安全筛选器[Private] =1)。当然,你现在可以实施更复杂的规则。例如,如果你想对更高的工资水平进行保护,则可以在Employee(private)上使用以下安全筛选器。
([Paylevel] <= 33 && [Private] = 1)
||
([Paylevel] > 33 && [Private] = 0)
结合上面讨论的部门和团队筛选器,其结果如图5.31所示。
图5.31 按团队和薪级分列的薪资成本,保护较高的薪资水平
请注意,对于高于33级的薪酬水平,我们不仅看不到任何结果,而且这些数字也没有累加。不过等等,难道不应该在34级以上的结果中得到一个空行吗?当你仔细查看安全筛选器时,你会注意到否定子句不执行任何操作。Employee(private)表中没有Paylevel 34及以上且 Private= 0 的行:毕竟,用Private= 0表示的行已经说明其他列为空!这意味着我们不是仅仅隐藏了一半的行,而是隐藏了更多的行。
如果你喜欢它的输出,那当然没问题。否则,则必须确定哪些员工的工资水平较高,并根据他们的员工人数进行筛选。执行此操作时(我们使用一些员工编号的延迟枚举),将显示这些空白值。
图5.32 按团队和薪级分列的薪金成本,保护较高的薪资水平,并显示为空白
正如你所看到的那样,让一切都按照你所期望的方式工作并不容易。我们现在不仅在Europe 2团队中看到空白,其他团队也显示了空白。毕竟,我们确实有权限查看到这些团队。这些团队中的所有员工都分组在空白薪资水平,因此薪资成本度量的逻辑决定了我们正在查看团队的所有员工并返回团队的薪资成本。
通过微调Employee(private)上的安全筛选器,你甚至可以进一步控制这一点,让空白输出仅针对团队 Europe 2显示。我们将这个问题留待你自己来探究解决。
上面,我们已经讨论了基于每个事实表的聚合级别。实现聚合级别安全性的另一种方法是将聚合级别视为属性。这样,所有连接的事实表以及因此的所有度量值都受安全策略的约束。这种方法不如我们之前的方法灵活,但好处是你不必编写特定的 DAX 度量值,并且更容易设置。
本节内容提供实例文件下载。如需下载请参考异步社区本书页面配套资源的“2.1 Aggregation security2.pbix”文件。 |
---|
这里的基本思想是,当我们想要确保单个员工级别的输出时,我们可以将每个员工属性视为私有属性。换句话说,公共的Employee表将仅包含主键(在我们的示例中为EmpNr和TeamNr列),所有其他列将移动到 Employee(private)表,如图5.33所示。通过本章中介绍的值级别安全性方法,员工数据可以得到有效保护。
图5.33 只保留 Employee 表中的主键
假设你要实现这样的安全需求:只能查看欧洲部门的数据;只有Europe 2队才能在员工层面上看到它。这可以通过Team和 Employee (private)表上的两个安全筛选器来完成。在 Team 表上设置如下筛选器。
[Division] = "Europe"
在Employee(private)表上(此处用到了一个事实,团队 Europe 2在Employee表的TeamNr列上值为9)按照如下这样设置。
(RELATED(Employee[TeamNr]) = 9 && [Private] = 1)
||
(RELATED(Employee[TeamNr]) <> 9 && [Private] = 0)
通过一个简单的销售度量,你可以获得图5.34所示的结果。
图5.34 使用 VLS 的安全策略的结果
同样,你可以通过拆分 Employee (private)表并创建Employee(very private)表来保护员工的某些属性(尽管此时我们建议使用不同的命名方案)。这与之前讨论过的不同私有属性集的解决方案的原理完全相同。
这两个表都与原始Employee表建立关系,如图5.35所示。
图5.35 添加Employee(very private)表
现在,你可以对Employee(very private)表应用不同的安全筛选器,例如,使用正/反筛选器结构,仅允许访问雇员 10220 的非常私密的数据,代码如下。
([EmpNr] = 10220 && [Private] = 1)
||
([EmpNr] <> 10220 && [Private] = 0)
表视觉对象中的结果如图5.36所示。
图5.36 访问员工的私密数据
这种保护聚合级别的方法不是 100% 安全的,因为事实表仍然使用员工级别的粒度。自助服务用户可以使用如下公式编写度量值来检索特定员工的结果。
Sales 10201 =CALCULATE([Sales],fSales[EmpNr] = 10201)
不过,想要做到这一点,用户必须知道使用了数字10201以及它代表什么含义。如果你要实现此方法,则至少不应使用组织中众所周知的业务关键字(如员工编号)。
通过阅读本章内容,相信你已从多个方面了解如何保护 Power BI 模型。行级别安全性的功能非常有用,主要是因为你可以使用 DAX 实现复杂的安全筛选器。
在 Power BI 模型中实现安全性时,需要仔细设计,这主要是因为模型可能具有多个安全角色,并且用户可能是多个角色的成员。并非所有安全角色都可以在同一模型中有效地组合,因此安全性甚至会影响拆分模型的决策。
使用 DAX,你可以检索用户的标识,并使用它来确定哪些数据是可见的,从而实现高度个性化的安全设置。你甚至可以使用 DAX 里的 PATH函数导航组织的层次结构。
你还了解到,通过建模、DAX 和行级别安全性的有效组合,你可以实现其他形式的安全性,例如用于保护属性的值级别安全性,以及用于保护聚合级别。
总体结论是:DAX 的强大功能不仅表现在它作为一门数据分析语言,而且在实现安全策略方面也具有巨大的功能。
在下一章中,我们将重点介绍一个完全不同的主题:可视化效果,以及如何使这些可视化效果比 Power BI本身的视觉对象更具动态性。
本文分享自 PowerBI生命管理大师学谦 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有