使用 Assert 方法
Assert 是一个可对代码访问权限类和 PermissionSet 类调用的方法。 可以使用 Assert 来允许代码(和下游调用方)执行其有权执行但其调用方无权执行的操作。 安全断言改变了运行时在安全检查期间执行的正常流程。 当您断言一个权限时,即通知安全系统不要检查您的代码的调用方是否有断言的权限。
警告 |
---|
请谨慎使用断言,因为它们会打开安全漏洞,并会破坏运行时实施安全限制的机制。 |
在库调用非托管代码或进行需要某权限的调用而该权限与库的本来用途不明显相关时,断言很有用。 例如,调用非托管代码的所有托管代码必须具有指定了 UnmanagedCode 标志的 SecurityPermission。 默认情况下,将不向不是从本地计算机产生的代码(例如从本地 Intranet 下载的代码)授予此权限。 因此,为了使从本地 Intranet 下载的代码能够调用使用非托管代码的库,它必须让库断言该权限。 另外,某些库可能会进行调用方看不到而又需要特殊权限的调用。
还可以在您的代码以一种完全对调用方隐藏的方式访问资源时使用断言。 例如,假定您的库从数据库中获取信息,但在该过程中还从计算机注册表中读取信息。 因为使用您的库的开发人员无权访问您的源代码,所以他们无从知道他们的代码需要 RegistryPermission 才能使用您的代码。 在这种情况下,如果您确定要求您的代码的调用方拥有访问注册表的权限不合理或不必要,则可以断言用来读取注册表的权限。 在这种情况下,由该库断言权限就是恰当的,这样可以使没有 RegistryPermission 的调用方能够使用该库。
只有在断言的权限和下游调用方要求的权限属于同一类型并且要求的权限是断言的权限的子集时,断言才会影响堆栈步。 例如,如果您断言用来读取 C 驱动器上的所有文件的 FileIOPermission,同时发出了一个下游要求,要求读取 C:\Temp 中的文件的 FileIOPermission,则断言会影响堆栈步;但是,如果要求写入 C 驱动器的 FileIOPermission,则断言无效。
若要执行断言,必须向代码同时授予要断言的权限以及表示执行断言的权限的 SecurityPermission。 虽然您可以断言尚未授予您的代码的权限,但此断言是无效的,因为在您的断言还没有能够使安全检查成功之前,安全检查就会失败。
下图说明在使用 Assert 时会发生什么情况。 假定下面有关程序集 A、B、C、E 和 F 以及两个权限 P1 和 P1a 的语句为真:
P1a 表示读取 C 驱动器上 .txt 文件的权限。
P1 表示读取 C 驱动器上所有文件的权限。
P1a 和 P1 都是 FileIOPermission 类型,P1a 是 P1 的子集。
已将 P1a 权限授予程序集 E 和 F。
已将 P1 权限授予程序集 C。
P1 和 P1a 两个权限均未授予程序集 A 和 B。
方法 A 包含在程序集 A 中,方法 B 包含在程序集 B 中,依此类推。
使用 Assert
在此方案中,方法 A 调用 B,B 调用 C,C 调用 E,E 调用 F。 方法 C 断言读取 C 驱动器上的文件的权限(权限 P1),方法 E 请求读取 C 驱动器上的 .txt 文件的权限(权限 P1A)。 在运行时遇到 F 中的要求时,就会执行堆栈步来从 E 开始检查 F 的所有调用方的权限。 E 已被授予 P1A 权限,因此堆栈步将继续检查 C 的权限,然后会在其中发现 C 的断言。 因为请求的权限 (P1a) 是断言的权限 (P1) 的子集,所以堆栈步会停止,安全检查自动成功。 没有将权限 P1a 授予程序集 A 和 B 并不重要。 通过断言 P1,即使没有授予调用方访问 P1 所保护资源的权限,方法 C 也可确保其调用方能够访问该资源。
如果设计类库,且有一个类访问受保护的资源,则在多数情况下应该安全要求(要求该类的调用方拥有适当的权限)。 如果该类随后会执行一个操作,您知道此操作的多数调用方没有相应的权限,并且您愿意承担责任让这些调用方调用您的代码,则可以通过对一个权限对象(该对象表示代码所执行的操作)调用 Assert 方法来断言该权限。 以此方式使用 Assert 可以使通常未能调用您代码的调用方能够调用您的代码。 因此,如果断言权限,则务必提前执行适当的安全检查以防止您的组件被滥用。
例如,假定您的高度信任的库类有一个删除文件的方法。 它通过调用非托管 Win32 函数来访问文件。 调用方将调用代码的 Delete 方法,并传入要删除的文件的名称 C:\Test.txt。 在 Delete 方法内部,代码会创建一个表示对 C:\Test.txt 的写访问权限的 FileIOPermission 对象。 (写访问权限是删除文件所必需的)。您的代码然后通过调用 FileIOPermission 对象的 Demand 方法来调用强制安全检查。 如果调用堆栈中的一个调用方没有此权限,则将引发 SecurityException。 如果未引发异常,则可以断定所有调用方都具有访问 C:\Test.txt 的权限。 由于您认为大多数调用方都将无权访问非托管代码,因此您的代码会创建一个表示调用非托管代码的权限的 SecurityPermission 对象,并调用该对象的 Assert 方法。 最后,它调用非托管的 Win32 函数来删除 C:\Text.txt 并将控制焦点返回给调用方。
警告 |
---|
您必须确保您的代码不在其他代码可以使用您的代码来访问您断言的权限所保护的资源时使用断言。例如,在写入一个文件(该文件的名称由调用方以参数形式指定)的代码中,您不能断言用于写入文件的 FileIOPermission,否则,您的代码会任凭第三方滥用。 |
当使用命令式安全语法时,在同一方法中对多个权限调用 Assert 方法会引发安全异常。 相反,您应当创建一个 PermissionSet 对象,向它传递您要调用的各个权限,然后对该 PermissionSet 对象调用 Assert 方法。 当使用声明式安全语法时,可以多次调用 Assert 方法。
下面的示例演示使用 Assert 方法重写安全检查的声明式语法。 请注意,FileIOPermissionAttribute 语法采用了两个值:一个是 SecurityAction 枚举,另一个是要向其授予权限的文件位置或目录。 即使不检查调用方是否有权限访问 C:\Log.txt,调用 Assert 也可使访问该文件的要求成功。
[Visual Basic]
Option Explicit
Option Strict
Imports System
Imports System.IO
Imports System.Security.Permissions
Namespace LogUtil
Public Class Log
Public Sub New()
End Sub
<FileIOPermission(SecurityAction.Assert, All := "C:\Log.txt")> Public Sub
MakeLog()
Dim TextStream As New StreamWriter("C:\Log.txt")
TextStream.WriteLine("This Log was created on {0}", DateTime.Now) '
TextStream.Close()
End Sub
End Class
End Namespace
namespace LogUtil
{
using System;
using System.IO;
using System.Security.Permissions;
public class Log
{
public Log()
{
}
[FileIOPermission(SecurityAction.Assert, All = @"C:\Log.txt")]
public void MakeLog()
{
StreamWriter TextStream = new StreamWriter(@"C:\Log.txt");
TextStream.WriteLine("This Log was created on {0}", DateTime.Now);
TextStream.Close();
}
}
}
下面的代码段演示使用 Assert 方法重写安全检查的命令性语法。 在此示例中,声明了 FileIOPermission 对象的一个实例。 向其构造函数传递 FileIOPermissionAccess.AllAccess 来定义允许的访问类型,后面是描述文件位置的字符串。 定义了 FileIOPermission 对象后,您只需调用其 Assert 方法即可重写安全检查。
[Visual Basic]
Option Explicit
Option Strict
Imports System
Imports System.IO
Imports System.Security.Permissions
Namespace LogUtil
Public Class Log
Public Sub New()
End Sub 'New
Public Sub MakeLog()
Dim FilePermission As New FileIOPermission(FileIOPermissionAccess.AllAccess, "C:\Log.txt")
FilePermission.Assert()
Dim TextStream As New StreamWriter("C:\Log.txt")
TextStream.WriteLine("This Log was created on {0}", DateTime.Now)
TextStream.Close()
End Sub
End Class
End Namespace
namespace LogUtil
{
using System;
using System.IO;
using System.Security.Permissions;
public class Log
{
public Log()
{
}
public void MakeLog()
{
FileIOPermission FilePermission = new FileIOPermission(FileIOPermissionAccess.AllAccess,@"C:\Log.txt");
FilePermission.Assert();
StreamWriter TextStream = new StreamWriter(@"C:\Log.txt");
TextStream.WriteLine("This Log was created on {0}", DateTime.Now);
TextStream.Close();
}
}
}