使用 EXECUTE AS 扩展数据库模拟

SQL Server 支持使用独立的 EXECUTE AS 语句显式模拟另一主体或在模块上使用 EXECUTE AS 子句隐式模拟另一主体的能力。通过使用 EXECUTE AS LOGIN 语句,独立的 EXECUTE AS 语句可用于模拟服务器级的主体或登录帐户。通过使用 EXECUTE AS USER 语句,独立的 EXECUTE AS 语句还可用于模拟数据库级的主体或用户。

通过模块上的 EXECUTE AS 子句执行的隐式模拟模拟的是在数据库级或服务器级指定的用户或登录帐户。此模拟取决于模块是数据库级模块(如存储过程或函数)还是服务器级模块(如服务器级触发器)。

了解模拟作用域

当使用 EXECUTE AS LOGIN 语句模拟主体或使用 EXECUTE AS 子句在服务器范围模块内模拟主体时,模拟的作用域为服务器范围。这意味着上下文切换后,可以访问模拟的登录帐户在服务器内具有权限的任何资源。

然而,当使用 EXECUTE AS USER 语句模拟主体或使用 EXECUTE AS 子句在数据库范围模块内模拟主体时,默认情况下,模拟的作用域仅限于数据库。这意味着引用数据库范围外的对象将返回错误。若要了解此默认行为的原因,请考虑以下情况。

在数据库内具有完全权限的数据库所有者有可能在数据库外不具有任何权限。因此,SQL Server 不允许数据库所有者模拟或使其他人有权模拟另一用户来访问数据库所有者当前权限范围之外的资源。

例如,假设一个宿主环境中有两个数据库,每个数据库都属于一个单独的所属实体。Database1 为 Bob 所有,Database2 为 Fred 所有。Bob 和 Fred 都不想让其他人访问其各自数据库内的资源。作为Database1 的所有者,Bob 可以在其数据库中为 Fred 创建一个用户,因为他在Database 1 内拥有完全权限,所以 Bob 还可以模拟用户 Fred。但是,由于 SQL Server 所规定的安全限制,Bob 无法在模拟上下文下访问 Fred 的数据库。如果没有设置这些默认限制,Bob 将能够在 Fred 不知道的情况下访问其数据。这就是默认情况下,数据库级模拟的作用域限制在数据库以内的原因。

但是,在某些情况下,有选择地将模拟作用域扩展到数据库之外可能会非常有用。例如,使用两个数据库并要求从一个数据库访问另一个数据库的应用程序就属于这种情况。

假设有这样一个营销应用程序:它调用 Marketing 数据库中名为 GetSalesProjections 的存储过程,该存储过程中定义有执行上下文切换。该存储过程调入 Sales 数据库来从 SalesStats 表中检索销售信息。默认情况下,此方案不可行,因为在数据库内建立的执行上下文在该数据库外无效。但是,营销应用程序的开发人员不想让营销应用程序的用户直接访问 Sales 数据库或具有该数据库内任何对象的权限。理想的解决方案是在存储过程中使用 EXECUTE AS 子句来模拟具有 Sales 数据库中所需权限的用户。但是,当前设置的默认限制将阻止实现此操作。因此,开发人员应想办法解决这一问题。

在 SQL Server 中,可以通过在两个数据库之间建立信任模型来有选择地扩展数据库内建立的数据库模拟的作用域。但是,在说明此信任模型以及如何有选择地扩展模拟作用域之前,您应该先了解 SQL Server 中的身份验证以及身份验证者的角色。

了解身份验证者

身份验证是特定主体确定并向系统证明其身份的过程。身份验证者是验证或证明特定主体身份的真实性的实体。例如,与 SQL Server 建立连接时,SQL Server 实例将对为此连接建立的登录帐户进行身份验证。

假设用户使用 EXECUTE AS LOGIN 语句在服务器级显式切换上下文。这要求在服务器级具有模拟权限。这些权限使权限的被授权者(即 EXECUTE AS LOGIN 语句的调用方)能够在 SQL Server 实例内的任何位置模拟指定的登录帐户。实际上,该语句使调用方能够作为被模拟的登录帐户模拟登录操作。服务器级范围权限的所有者是拥有 SQL Server 实例的 sysadmin。在这种服务器级模拟情况下,身份验证者是 sysadmin 或 SQL Server 实例本身。

假设有另一种情况:由于在数据库范围模块上使用 EXECUTE AS USER 语句或 EXECUTE AS 子句,因此上下文被建立。在这些情况中,会检查数据库范围内的模拟权限。用户的 IMPERSONATE 权限的默认范围是数据库本身,该数据库的所有者是 dbo。另外,这些模拟操作的身份验证者是数据库所有者。实际上,确定被模拟用户的身份并证明其真实性的也是数据库所有者。因为数据库所有者拥有整个数据库,所以模拟的上下文在该特定数据库内的任何位置都被认为是可信的。但是,在该数据库外,模拟上下文是无效的。

如何使用身份验证者

身份验证者用于确定建立的上下文在特定范围内是否有效。通常,身份验证者是系统管理员 (SA) 或 SQL Server 实例,或者对于数据库来说,它是 dbo。身份验证者实际上是在其中为特定用户或登录帐户建立上下文的范围的所有者。此身份验证者信息在为登录帐户和用户维护的标记信息内捕获,并可以通过 sys.user_tokensys.login_token 视图查看。有关详细信息,请参阅了解执行上下文

注意注意

如果标记视图中没有返回身份验证者信息,则身份验证者是 SQL Server 实例。没有上下文切换或在服务器级进行模拟时,将出现这种情况。

将某个范围的所有者作为其身份验证者的执行上下文在该范围内均有效。这是因为某范围(如数据库)内的所有实体都隐式信任该范围的所有者。该上下文在其他范围(例如,其他数据库或 SQL Server 实例本身)内也有效,在这些范围内,身份验证者是“可信”的。因此,数据库范围外的模拟用户上下文的有效性取决于上下文的身份验证者在目标范围内是否可信。如果目标范围是另一数据库,则通过授予身份验证者 AUTHENTICATE 权限建立此信任;如果目标范围是 SQL Server 实例,则通过授予身份验证者 AUTHENTICATE SERVER 权限建立此信任。

扩展模拟作用域

若要将模拟作用域从数据库内扩展到目标范围(如另一数据库或 SQL Server 实例),必须满足下列条件。

  • 身份验证者在目标范围内必须是可信的。

  • 源数据库必须标记为可信。

信任身份验证者

使用前面的 SalesMarketing 数据库示例,下图显示 Marketing 数据库中的存储过程 GetSalesProjections 访问 Sales 数据库中的 SalesStats 表中的数据。该存储过程包含子句 EXECUTE AS USER MarketingExecSales 数据库的所有者是 SalesDBOMarketing 数据库的所有者是 MarketingDBO

EXECUTE AS 切换模块的执行上下文

用户调用 GetSalesProjections 存储过程时,EXECUTE AS 子句将存储过程的执行上下文从调用用户隐式切换到 MarketingExec 用户。此上下文的身份验证者是 MarketingDBO,即 Marketing 数据库的所有者。默认情况下,此过程可以访问 Marketing 数据库内允许 MarketingExec 用户访问的任何资源。但是,为了访问 Sales 数据库中的表,Sales 数据库必须信任身份验证者 MarketingDBO

可以通过以下方法实现此操作:在 Sales 数据库中创建一个名为 MarketingDBO 的用户,并将其映射到 MarketingDBO 登录帐户,然后授予该用户 Sales 数据库的 AUTHENTICATE 权限。这样,将此权限的被授权者作为其身份验证者的任何执行上下文在该数据库内有效。因为对身份验证者 MarketingDBO 授予了 Sales 数据库的 AUTHENTICATE 权限,所以由 Marketing 数据库中 GetSalesProjections 存储过程中的 EXECUTE AS 子句建立的 MarketingExec 用户的上下文在 Sales 数据库中是可信的。

此示例说明了扩展模拟作用域以访问外部数据库中的对象,还可以将模拟作用域扩展到 SQL Server 实例。例如,如果此过程要创建一个登录帐户且此服务器级操作需要服务器范围权限,则必须对上下文的身份验证者授予 AUTHENTICATE SERVER 权限。也就是说,将 AUTHENTICATE SERVER 权限的被授权者作为其身份验证者的任何上下文在整个 SQL Server 实例内是可信的,就好像上下文直接登录到 SQL Server 实例一样。

信任数据库

在 SQL Server 中,为了为扩展数据库级模拟作用域操作提供进一步的安全性和粒度,信任模型有了进一步的发展。可以将 AUTHENTICATE 权限用作使目标范围信任上下文的身份验证者的一种方法,而且还可以确定 SQL Server 实例是否信任源数据库以及其中的内容。

为举例说明此问题,假设 MarketingDBO 主体拥有名为 Conference 的另一数据库,并假设 MarketingDBO 要使在 Marketing 数据库内指定的执行上下文具有访问 Sales 数据库中资源的权限。但是,它不想让 Conference 数据库中建立的任何上下文具有访问 Sales 数据库的任何权限。

若要达到此要求,必须将包含以下模块的数据库标记为可信:在此模块中,模拟上下文用于访问数据库外的资源。TRUSTWORTHY 属性指明 SQL Server 实例是否信任数据库以及其中的内容。TRUSTWORTHY 属性有两种用途:

  1. 减少来自附加到 SQL Server 实例并可能包含恶意模块(定义为在高特权用户的上下文下执行)的数据库的威胁。

    可以通过确保默认情况下附加的数据库未标记为可信来实现此目的。还可以通过确保通过可能包含的恶意模块访问数据库外的资源时要求将数据库标记为可信来实现此目的。只有 sysadmin 固定服务器角色的成员可以设置数据库中的 TRUSTWORTHY 属性。

  2. 使 SQL Server 实例的管理员可以区分允许访问外部资源的那些数据库,以及当数据库具有相同的所有者且在某范围内该所有者被信任为身份验证者时不允许访问外部资源的那些数据库。

可以使用 TRUSTWORTHY 属性控制此行为。例如,假设有一种情况,其中一个数据库 (Database 1) 的模拟上下文应该被信任,而另一数据库 (Database 2) 的模拟上下文不应该被信任,并且两个数据库具有相同的所有者,在目标范围内该所有者被信任为身份验证者。可以将 Database 1 的 TRUSTWORTHY 属性设置为 ON,将 Database 2 的 TRUSTWORTHY 属性设置为 OFF,以便确保 Database 2 中的模块无法访问该数据库外的资源。

下图说明使用 TRUSTWORTHY 数据库属性来控制对源数据库范围外的资源的访问。MarketingDBOSales 数据库中被授予 AUTHENTICATE 权限,它是 Marketing 数据库和 Conference 数据库的所有者。Marketing 数据库中的 GetSalesProjections 存储过程可以成功访问 Sales 数据库,因为它满足以下两个安全要求:身份验证者 MarketingDBO 在目标范围内被信任,并且源数据库 Marketing 是可信的。尝试从 Conference 数据库中访问 Sales 数据库被拒绝,因为只满足了一个要求:身份验证者 MarketingDBO 在目标范围内被信任。

控制对外部资源的数据库访问

无论何时使用模拟上下文尝试访问数据库范围外的资源,SQL Server 实例都要验证发起请求的数据库是可信的且身份验证者被信任。

作为身份验证者的证书和非对称密钥

通过将数据库所有者用作身份验证者,可以扩展在数据库内建立的模拟上下文来访问该数据库范围外的资源。这要求外部资源信任数据库所有者,并且数据库本身也是可信的。但是,此方法意味着当对可信的数据库所有者授予目标范围内的 AUTHENTICATE 或 AUTHENTICATE SERVER 权限且调用数据库可信时,在信任数据库所有者的整个目标范围内,该数据库中建立的任何模拟上下文都是有效的。

可能需要更高的信任粒度级别。假设业务要求通过使用 EXECUTE AS 子句访问目标资源来指明信任源数据库中的几个模块,而不是信任整个源数据库。例如,假设 SalesDBO 想要确保仅 GetSalesProjections 存储过程可以作为 MarketingExec 用户访问 SalesStats 表,而不想让 Marketing 数据库中的每个用户都具有能够访问 Sales 数据库中资源的 MarketingExec 的模拟权限。信任 MarketingDBO 并将 Marketing 数据库设置为可信并不能完成该任务。为了提供其他粒度级别来实现该要求,信任模型允许使用证书非对称密钥作为身份验证者。此操作利用了称为“签名”的一种技术。有关签名的详细信息,请参阅 ADD SIGNATURE (Transact-SQL)

使用签名

模块的签名可以确保只有有权访问用来为模块签名的私钥的人员能够修改模块内的代码。考虑到签名过程的安全性,可以信任签名中指定的证书或非对称密钥。更确切地说,可以信任证书或非对称密钥的所有者,而不仅仅是数据库所有者。

通过在目标范围内为映射到证书或非对称密钥的用户授予 AUTHENTICATE 或 AUTHENTICATE SERVER 权限,可以实现信任签名模块。

通过此方法,使用可信证书签名的模块内建立的执行上下文在目标范围内有效,在此范围内,证书是可信的。

例如,假设使用名为 C1 的证书签名存储过程 GetSalesProjectionsSales 数据库中必须存在证书 C1,并且必须将一个用户(例如 CertUser1)映射到证书 C1。然后,在 Sales 数据库中对 CertUser1 授予 AUTHENTICATE 权限。

调用过程时,将验证其签名以确保签名时此过程没有被篡改。如果验证了签名,模块中的 EXECUTE AS 子句建立的上下文将把证书 C1 作为其身份验证者。如果未验证签名,不会将身份验证者添加到标记中且尝试访问外部资源将失败。

下图说明使用签名的模块来控制对源数据库范围外的资源的访问。通过使用名为 C1 的证书签名 Marketing 数据库中的存储过程 GetSalesProjectionsSales 数据库中存在证书 C1 且用户 CertUser 已映射到该证书。在 Sales 数据库中,CertUser1 被授予 AUTHENTICATE 权限。

用于限制数据库访问的证书

验证此身份验证者是否可信的方式与数据库所有者作为身份验证者时的验证方式相同。也就是说,通过检查 AUTHENTICATE SERVER 或 AUTHENTICATE 权限来验证。但是,因为信任建立在粒度级别上且不修改签名就无法更改模块,所以无需验证数据库的 TRUSTWORTHY 属性。

这会减少来自包含恶意代码的附加数据库的隐患。攻击者必须使用与可信证书对应的私钥给模块签名。但是,攻击者没有访问此密钥的权限。而且,如果修改现有的可信模块或创建新模块,该模块将不包含有效的可信签名。

有关详细信息,请参阅模块签名(数据库引擎)

扩展数据库模拟作用域的规则

概括而言,当且仅当满足下列条件时,可以将数据库内建立的上下文的模拟作用域扩展到其他范围:

  • 身份验证者(数据库所有者或用于给模块签名的证书或非对称密钥)必须在目标范围内是可信的。可以通过对映射到数据库所有者、证书或非对称密钥的主体授予 AUTHENTICATE 或 AUTHENTICATE SERVER 权限来实现此目的。

  • 如果身份验证者是数据库所有者,则源数据库必须标记为可信。可以通过将数据库的 TRUSTWORTHY 属性设置为 ON 来实现此目的。

根据需要选择信任机制

数据库所有者方法和签名方法都各有其优缺点。适合您需要的最佳机制取决于您的业务要求和业务环境。

数据库所有者方法

用于建立信任的数据库所有者方法具有下列优缺点:

  • 不要求了解任何密码系统概念,如证书或签名。

  • 提供的粒度不像基于签名的方法提供的粒度那样多。

  • 将数据库附加到 SQL Server 实例会将数据库的 TRUSTWORTHY 属性设置为 OFF。在系统管理员将 TRUSTWORTHY 属性显式设置为 ON 之前,数据库所有者信任的模块将一直无效。这意味着在附加的数据库能够根据需要运行且访问其他数据库之前,系统管理员可能需要一些干预。

签名方法

用于建立信任的签名方法具有下列优缺点:

  • 可以提供信任粒度级别,但只适用于在签名的模块内执行的上下文切换。

  • 签名无法应用于通过独立的语句 EXECUTE AS USER 和 EXECUTE AS LOGIN 建立的上下文切换。这些语句需要基于数据库所有者的方法扩展信任范围。

  • 应用程序供应商和开发人员可以使用私钥给模块签名,但在发送模块或数据库之前必须删除该私钥。之所以这样做,是因为私钥仅用于给模块签名。若要验证签名,使用与模块关联的公钥就足够了。

  • 由于模块的签名,附加数据库不会影响可信模块。如果没有其他要求,这些签名将生效。