在中间层 .NET 应用程序中经过受权管理器使用基于角色的安全

基于角色的安全是从 Windows NT 的第一个版本开始在 Windows 平台上发展而来的。使用角色,操做系统能够经过检查称为 BUILTIN\Administrators 的组的安全上下文作出一些决定,例如,进程是否有特权。操做系统基于该逻辑角色作出决定(例如,是否让您安装服务或设备驱动程序)。在安装操做系统时,您能够经过将相应的用户添加到 Administrators 组来选择谁将承担该角色。程序员

Microsoft 事务服务 (MTS) 和 COM+ 试图使基于角色的安全成为一种让应用程序开发人员感受愉快的功能,而且为 COM 服务器提供了一个简单的、基于角色的受权基础结构。目标是为多层服务器应用程序启用受信任的子系统模型,其中应用程序服务器受到后端资源的信任以批准请求。经过及早执行受权,能够避免向后端服务器委托客户端凭据的须要。委托充满了从潜在的安全漏洞到可伸缩性问题等大量问题。web

若是您一直在寻找中间层中的通用受权解决方案,那么您的搜索能够告一段落了。后端

受权管理器简介

受权管理器(一般称为 AzMan)是 Windows 的一种通用的、基于角色的新安全体系结构。AzMan 与 COM+ 无关,所以它能够用在任何须要基于角色的受权的应用程序中,包括 ASP.NET Web 应用程序或 Web 服务、基于 .NET Remoting 的客户-服务器系统等等。在撰写本文时,受权管理器仅在 Windows Server?200三、Windows 2000 的 Service Pack 4 中提供,而且预计做为 Windows XP 将来的 Service Pack 发布。api

AzMan 有两个部分:运行库和管理 UI。运行库由 AZROLES.DLL 提供,它公开了一组供那些利用基于角色的安全的应用程序使用的 COM 接口。管理 UI 是一个 MMC 管理单元,您能够经过运行 AZMAN.MSC 或者经过向您选择的 MMC 控制台中添加受权管理器管理单元对其进行试验。(请注意,管理 UI 与较旧的平台不兼容,所以您将须要使用运行 Windows Server 2003 的计算机来管理 AzMan。)[编辑更新 — 1/9/2004Windows Server 2003 管理工具包使您能够在运行 Windows XP Professional 的计算机上安装 Windows Server 2003 管理工具。]数组

当运行图 1 中显示的 AzMan 管理工具时,您将注意到的第一件事情是它比 COM+ 提供的功能复杂得多。您再也不只具备角色和角色分配操做。您如今具备能够分配给任务的低级别操做,而任务随后又能够分配给角色。任务能够包含其余任务,而角色能够包含其余角色。这一分层方法有助于改进目前复杂的应用程序中所需的好像不受限制的角色集。缓存

图 1 管理管理器安全

如下是任务和角色的建立方式。应用程序设计器定义了被视为安全敏感的整个低级别操做集。而后,该设计器定义了一组映射到这些操做的任务。任务被设计为可由业务分析师理解,所以一个给定任务老是由一个或多个低级别操做组成。若是用户被授予执行某个任务的权限,则他或她就被授予了执行该任务中全部操做的权限。做为一个示例,一个名为“提交采购定单”的任务可能由下列操做组成:得到下一个 PO 编号、将 PO 排队和发送通知。固然,您能够老是简单地将每一个任务映射到单个操做,以使事情尽量保持简单,可是若是您须要的话,则能够利用分隔任务和操做所具备的灵活性。服务器

在定义任务和操做以后,就能够开始编码了,而且不管什么时候须要执行敏感操做,均可以包含对 AzMan 运行库的调用。该调用是 IAzClientContext.AccessCheck,稍后我将说明一个有关它的用法的示例。并发

在部署时,应用程序安装程序会设置一个 AzMan 存储区(做为 Active Directory? 的一部分,或者在简单的 XML 文件中),并安装基本的低级别操做和任务。管理员使用 AzMan 管理单元来查看该应用程序的任务的定义和说明。而后,管理员定义对于他/她的组织有意义的角色。就像任务被定义为一组低级别操做同样,角色一般被定义为一组任务。而后,管理员能够向这些角色分配用户和组。实际上,从这时开始,管理员在维护应用程序方面的主要工做将是随着人员加入或离开公司或者更改职衔,在角色中添加和移除用户。app

迄今为止,我已经重点介绍了应用程序开发人员和管理员,可是实际上可能存在第三个帮助部署的人 — 业务逻辑脚本撰写者。每一个任务均可以具备关联的脚本。这里的思路是:找到一般经过调用 IsCallerInRole 制定的动态安全决策,将它们移出应用程序代码并移至某个位置,以便管理员无需修改和从新编译代码就能够对应用程序的安全策略进行更改。

返回页首返回页首

示例应用程序:公司库

让咱们观察一个示例。假设您要构建一个系统以管理公司图书馆。您须要可以管理书籍库存以及书籍的借阅和归还等等。您将使用 AzMan 实现基于角色的安全。

首先,您须要建立设计中出现的敏感操做列表:

阅读目录

占位(为本身或他人)

借书

还书

将书籍添加到库存

从库存中移除书籍

阅读顾客历史记录(为本身或他人)

请注意,有几个操做对只在运行时才会具备的信息敏感。例如,当试图阅读顾客的历史记录时,应用程序必须提供上下文信息,指示用户是在试图访问他/她本身的历史记录仍是他人的历史记录。当创建原型时,可使用 AzMan 管理单元将这些操做添加到简单的 XML 存储区。图 1 显示了这一添加过程。

若是您要尝试本身继续操做,请运行 AZMAN.MSC,而且经过“Action | Options”菜单确保您处于开发人员模式。在 XML 文件中建立一个新的存储区,而后在该存储区中建立一个新的应用程序。接下来,逐个地添加操做,而且赋予它们名称和表明操做编号的惟一整数。应用程序开发人员使用该编号来标识 AccessCheck 调用中的操做。请注意,在命名操做时,我已经用前缀“op.”对名称进行了编码。这只是为了在稍后建立任务和角色时避免命名冲突,由于这些名称所有来自相同的池而且必须是惟一的。

AzMan 管理单元在两种模式下操做:开发人员和管理员。在管理员模式下,您没有选择建立存储区或应用程序的自由,而且您不能改动应用程序代码所依赖的低级别操做定义。坦白地说,没有什么东西可以妨碍系统管理员进入开发人员模式并完成这些操做,但要点是在管理员模式下,UI 中选项的数量将减小,以简化管理员的工做并帮助他们避免错误。

图 2 AzMan 中的任务定义

下一个步骤是定义一组映射到这些低级别操做的任务,以便管理员可以轻松定义角色。由于您使操做列表保持简单,因此能够为每一个操做定义单个任务。除非您绝对须要更高的复杂性,不然有一个保持事情简单的很好的理由,而且它必须与业务逻辑脚本有关 — 我稍后才会对其进行详细讨论。所以,如今让咱们定义一系列基本上与个人操做相同的任务。图 2 显示了在 AzMan 中编辑任务定义时的工做模式。

返回页首返回页首

受权存储区

如今是改变您的身份并伪装您是部署应用程序的管理员的时候了。在“Action | Options”菜单下切换到管理员模式,而且注意 GUI 是如何更改的:您再也不可以编辑应用程序的低级别操做了。继续操做,而且按照图 3 中的定义为应用程序添加角色。

misauthorizationmanagerfig03

图 3 角色和任务

在这里,一种简化事情的方式是将角色嵌套。例如,能够根据 Patron 角色定义 Clerk,而且添加“借书”和“还书”任务,如图 4 中所示。请尝试用 COM+ 完成该工做。

图 4 嵌套角色

管理员须要作的最后一件事情是经过向这些抽象角色中添加真实的用户使它们变得具体。为此,请选择“Role Assignments”文件夹,并选择“Assign Roles”操做。请注意,角色只有在被添加到该文件夹以后,才会实际上变为活动角色。例如,IAzApplication.Roles 属性只返回已经添加到 Role Assignments 文件夹中的角色集合,而不是已经定义的全部角色。在分配角色以后,请当即右键单击它以添加 Windows 用户和组,或者添加您先前已经在 AzMan 存储区中定义的应用程序组。我将在本文的稍后部分对应用程序组进行介绍。

返回页首返回页首

AzMan 运行库接口

在定义了一些操做和任务以后,就能够开始在代码中实现访问检查了。您须要考虑的第一件事情是身份验证。若是您可使用一些内置的 Windows 管线(就像 Web 服务器对 Kerberos 身份验证的支持同样),则能够得到客户端的令牌。这是到目前为止使用 AzMan 的最普通的方式,由于令牌包含用户所在的全部组,从而能够快速地将该用户映射到一组 AzMan 角色。另外一方面,若是您要使用表单或 X.509 证书对用户进行身份验证,则您将不会具备令牌。相反,您将只具备该用户的名称。这并不意味着您没法使用 AzMan,甚至也不意味着您将必须编写更多的代码。可是这确实意味着将须要付出更大的代价,由于 AzMan 运行库将必须手动查找该用户的组。这会引发与域控制器之间的往返行程。

应用程序须要作的第一件事情是初始化 AzMan 运行库,使其指向它计划使用的存储区,而且向下探测到应用程序中存放受权设置的位置。如今,让咱们使用简单的基于 XML 的存储区:

AzAuthorizationStore store = new AzAuthorizationStoreClass();
store.Initialize(0, @"msxml://c:\MyStore.xml", null);
IAzApplication app = store.OpenApplication(
    "Corporate Library Application", null);

要生成该应用程序,项目须要引用 AzMan interop 程序集,它位于 %WINDIR%\Microsoft.NET\Framework\AuthMan 目录中。

既然要引导该应用程序,那么在对新的客户端进行身份验证时,您须要构建客户端的安全上下文的表示。该上下文在缓存用户的角色映射方面很是相似于令牌:

IAzClientContext ctx =
  app.InitializeClientContextFromToken(htoken, null);

到哪里去得到客户端的令牌?唔,这取决于您要编写哪一个类型的应用程序。例如,下面是某个 ASP.NET 页中的用于得到客户端令牌的 C# 代码。在该示例中,web.config 指定身份验证模式为“Windows”,而且已经将 IIS 配置为须要集成 Windows 身份验证:

WindowsIdentity id = (WindowsIdentity)User.Identity;
IntPtr htoken = id.Token;

若是您只知道客户端的名称而且不能访问它们的令牌,请尝试弄清楚是否存在您能够得到的令牌,由于令牌是发现客户端的组的最权威方式。就像我先前提到的那样,它仍是最快的方式。若是您确定本身没法得到该客户端的令牌,则请使用下面的备用方法来根据形如“域\用户”的账户名称来初始化上下文。该调用可能引发为发现域组而产生的往返行程,因此请作好须要花费一些时间来执行该操做的思想准备:

IAzClientContext ctx =
  app.InitializeClientContextFromName(name, null);

在具备客户端上下文之后,就能够运行访问检查。该调用采用不少参数,但如今我将使事情保持简单。让咱们假设您要实现一个向库存中添加书籍的函数。我将“向库存中添加书籍”定义为操做编号 5,所以代码可能如图 5 所示。

第一个参数 nameOfBook 是一个在启用运行库审核后使用的字符串。它标识您要对其执行操做的对象,所以您应当老是在这里提供一些有意义的信息。我已经使用了第二个参数 scopes 的默认值。稍后我将对该参数进行解释。第三个参数用于列出您要测试的一个或多个操做。结果是一个老是与操做数组大小相同的数组,带有与每一个操做相对应的整数状态代码,以指示是授予仍是拒绝访问权限。零表示访问检查成功,而且容许该上下文标识的客户端执行指定操做。其余任何值都表示失败(一般您将看到的是数字 5,即 ERROR_ACCESS_DENIED)。

AzMan 运行库接口不是强类型的。它对于本身的大多数参数都使用 VARIANT。这容许传统的使用脚本语言的 ASP 程序员使用 AzMan,可是意味着使用强类型语言(如 C# 和 Visual Basic .NET)的程序员可能在调用 AccessCheck 时犯下一些直到运行时才会发觉的错误。例如,操做数组的类型必须是 object[],而不是 int[],可是若是您传递 int[],则编译器不会抱怨,由于参数的实际类型是对象。当我一开始学习该 API 时,这个问题让我感到困惑,我花费了好长时间才弄清楚究竟应该如何编写代码才能避免出现因为参数类型不匹配而形成的运行时错误。我已经听到有传闻说最终将出现 AzMan 的托管接口,可是在此以前,您可能但愿为 AccessCheck 编写强类型的包装以免犯错误。下面的代码显示了一个示例,它还简化了调用该函数的最多见方式:

public class AzManWrapper {
  public static int AccessCheck(
             IClientContext ctx,
             string objectName,
             int operation) {
    object[] results = (object[])ctx.AccessCheck(
         objectName, scopes, ops,
         null, null, null, null, null);
    return (int)results[0];
  }
}

经过包装,能够提供本身的 AccessCheck 重载,以便处理在须要其余可选参数时出现的更复杂状况。特别地,为该函数使用包装应当可以减小不少困难,而且下降应用程序代码的混乱程度。您甚至可使用该包装将 AccessCheck 失败转换为异常,而不是与 IPermission.Demand 行一块儿返回状态代码。尽管如此,请不要过于执着以致于包装整套接口,由于该函数其实是惟一一个难以调用的函数。

此刻,您可能想知道的一件事情是:在使用 AzMan 时是否能够不使用 Windows 账户来表示用户。运行库在设计时考虑了该问题,尽管您须要为每一个用户定义自定义安全标识符 (SID) — 这不是很是困难,而且您必须调用一个备用方法来初始化客户端安全上下文,即 InitializeClientContextFromStringSid。最大的障碍是您将没法使用 AzMan 管理单元来管理存储区(它们与 Windows 用户和组很是紧密地耦合在一块儿)。有关如何处理该问题的详细信息,请参阅本文开头引用的由 Dave McPherson 撰写的白皮书。

返回页首返回页首

存储区、应用程序和范围

我但愿能回顾一些内容,稍微介绍一下受权存储区的结构。首先,您具备两个用于存储受权设置的选择:能够将整个存储区放到 XML 文件中,或者在 Active Directory 中承载它。我强烈建议对于生产应用程序使用 Active Directory,由于它比简单的 XML 文件提供了更多的功能,而且一般还会提供更好的性能。

若是您在实验室中具备能够利用的测试域,请尝试启动 AzMan 管理单元,在 Active Directory 中建立一个新的存储区,并赋予其以下所示的独特名称:“CN=MyStore, CN=Program Data, DC=MyDomain, DC=com”(将 MyDomain 替换为您本身的域)。要查看 AzMan 在 Active Directory 中建立了哪些对象,请使用诸如 ADSIEdit(这是一个能够从 Windows Server 2003 CD 中经过运行 SUPPORT\TOOLS\SUPTOOLS.MSI 安装的 MMC 管理单元)之类的工具。在该存储区中建立一个应用程序,而且启动新应用程序的属性页。您将注意到,该属性页上含有在使用简单 XML 文件时不存在的安全和审核选项卡。

在 Active Directory 存储区中,能够委托管理存储区中的单个应用程序的职责,而且能够在很是详细的级别审核对存储区进行的更改。使用 XML 文件,您会受到用 NTFS 权限和审核保护文件自己的限制。当前,只有在将存储区放置在目录中的时候,运行时审核才会受到支持,而审核对于大多数应用程序而言都是很是重要的。若是 Active Directory 可用,则我强烈敦促您将 AzMan 存储区放到它里面,由于它是用于承载 Windows 中安全策略的最佳处所。

单个存储区能够容纳多个应用程序。每一个应用程序都具备它本身的用于操做、任务和角色的命名空间。若是您在多个应用程序中共享存储区,则请注意并发问题,由于存储区尚不支持并发编辑。若是您认为两个管理员有可能同时编辑单个存储区,则须要提供一些外部锁定来序列化对该存储区的访问;不然,该存储区可能会被破坏。AzMan 管理单元不提供该功能,等到它提供该功能时,您最好限制每一个存储区的内容以免并发编辑。最简单的解决方案是将每一个存储区限制为容纳单个应用程序。

每一个应用程序还能够定义多个范围,这是受权管理器的一种高级功能,而且我只向已经进一步学习 AzMan 而且绝对须要这一额外级别复杂性的人们推荐该功能。范围使您能够对应用程序的不一样部分具备不一样的受权设置。例如,在大型 Web 应用程序中,能够在特定子目录下面以某种方式分配角色;在不一样的子目录下,可能以不一样方式分配角色,或者可能定义一组彻底不一样的角色。

在这种状况下,范围可能很方便,由于它们能够共享低级别的操做定义,甚至可能共享某些任务、角色和应用程序组。遗憾的是,它们还可能令人感到困惑而且容易滥用。例如,在调用 AccessCheck 时,可使用第二个参数指定要用于检查的范围。若是用户将提供范围名称(或许经过请求中的 URL),则您在将其传递给 AccessCheck 以前,最好可以确保该范围名称是规范化的;不然,聪明的用户可能经过以意外的方式对名称进行编码,欺骗您使用更脆弱的范围。若是您不熟悉这种类型的攻击,则应当阅读 Writing Secure Code, Second Edition (Microsoft Press, 2002) 中有关规范化的章节。要了解有关像范围这样的高级功能的详细信息,请参阅本文开头引用的由 Dave McPherson 撰写的白皮书。

返回页首返回页首

应用程序组

在 AzMan 中,有一种称为“应用程序组”的很是好的功能。在大型组织中,将新组添加到目录中以便应用程序使用可能很是辛苦。实际上,若是只有您的应用程序须要特定的组定义,则当疲惫不堪的域管理员拒绝向他/她的已经几乎没法管理的组列表中添加另一个条目时,那么您可能会很是不走运。在这种状况下,应用程序组能够拯救您。在存储区、应用程序或范围级别,能够定义用户组而且向它们分配逻辑组名称。而后,您能够在角色分配中使用这些应用程序组。

AzMan 提供了两种类型的应用程序组:基本组和轻量级目录访问协议 (LDAP) 查询组。基本组很是相似于 Active Directory 中的组,可是有一点儿不一样:能够定义包含成员和排除成员。例如,您能够定义一个名为 EveryoneButBob 的组,就像我在图 6 中所作的那样。优势是功能和方便性都获得了增长。缺点是肯定该应用程序组中的成员身份所需的 CPU 周期数以及存储应用程序组中的成员身份列表所需的内存都增长了,所以请谨慎使用该功能。若是喜欢图 6 中显示的排除功能,您仍然能够经过使用域组做为应用程序组中的成员来得到该功能,从而减小 AzMan 须要在内存中保持的成员身份列表的大小。

misauthorizationmanagerfig06

图 6 容许除一人 (Bob) 以外的全部人

LDAP 查询组是 AzMan 的一项代价高昂可是很不错的功能。在这里,您可使用 LDAP 查询语法定义在某种程度上相似的用户组。例如,如下是能够用来定义至少 21 岁的工程师集合的方式:

(&(age>=21)(memberOf= CN=eng,DC=foo,DC=com))

无论组的类型是基本组仍是 LDAP 查询组,管理员均可以使用这些应用程序组做为向角色分配用户的备用方式。

返回页首返回页首

脚本

对于静态受权不能知足须要的状况,应用程序能够用变量和对象引用的形式向 AccessCheck 提供额外的上下文。这使脚本编写者可使用 JScript? 或 VBScript 添加业务逻辑,而无需更改和从新编译应用程序。例如,能够向先前定义的阅读顾客历史记录任务提供额外的上下文(或许是用户所属的角色集),以及一个指示客户是访问他/她本身的历史记录仍是他人的历史记录的布尔值。这将使您可以编写脚本,以容许经理查看任何顾客的历史记录,可是普通顾客只能限于查看他们本身的历史记录。能够为该任务编写如图 7 中所示的脚本。

要将该脚本与阅读顾客历史记录任务相关联,请调出该任务的定义页,浏览到包含该脚本的文件,将语言指定为 VBScript,而后按“Reload Rule into Store”按钮。

返回页首返回页首

支持受权脚本

要支持图 7中显示的脚本,您将须要向任何涉及阅读顾客历史记录任务的访问检查多传递几个参数。因为您已经将事情简单化,而且每一个操做只定义了一个任务,所以这意味着每当您询问有关相应操做的状况时,均可以提供该上下文。如下是一个代码片断,它显示了如何为脚本编写者提供这些额外的上下文:

bool self = _userIsCheckingOwnHistory();
object[] operations = { opReviewPatronHistory };
object[] scopes     = { "" };
object[] varNames   = { "roles", "self" };
object[] varValues  = { ctx.GetRoles(""), self };
object[] results = (object[])
ctx.AccessCheck(nameOfPatronHistory,
                scopes, operations,
                varNames, varValues,
                null, null, null);

AzMan 对于 varNames 和 varValues 数组的顺序有一点儿挑剔。您必须像我同样按字母顺序对 varNames 进行排序。而后,varValues 数组必须为 varNames 中的每一个命名参数提供相应值,这一点很是明显。若是您要显得更加独出心裁,则可使用 AccessCheck 的最后三个参数传入命名的对象引用。这会将脚本编写者看到的对象模型扩展至默认的 AzBizRuleContext 对象之上。我将让您本身对该功能进行试验,以及解决您在决定支持脚本后可能遇到的一些难题。

您将可能注意到的有关脚本的第一件奇怪的事情是,它们是在任务和角色级别定义的,而不是在操做级别定义的。可是,应用程序程序员为单个操做执行访问检查以及提供上下文变量,所以脚本编写者如何知道将向给定任务提供哪些变量?很明显,应该由开发人员基于各个操做来仔细编写相关文档。一种策略是使事情真正简单化,而且不管要执行哪一个操做,都老是传递相同的上下文变量。这固然能够为脚本编写者简化事情。在个人图书馆示例中,我当心地针对每一个操做定义了一个任务,所以我能够为每一个任务自定义上下文变量,但请记住,管理员在管理模式下运行时能够定义新任务。若是系统管理员要定义一个新任务以便将两个分别提供不一样上下文变量的操做集成在一块儿,那么会发生什么状况呢?只需努力使事情简单化,而且仔细说明应该如何编写脚本以便避免出现这些使人讨厌的状况。

在编写脚本时须要警戒的另一件事情是,脚本的结果被缓存在客户端上下文对象中以便提升效率。扔掉客户端上下文时,就同时扔掉了缓存。了解这一点是有好处的,由于某些脚本或许依赖于可能随时间而更改的外部数据。

请注意,设计脚本的目的是使它们所附加到的任务或角色具备某种资格。例如,假设 Alice 是角色 R1 和 R2 的成员。角色 R1 被直接授予执行操做 X 的权限。角色 R2 被授予相同的权限,可是该受权是经过脚本进行的。当 Alice 试图执行操做 X 时,AzMan 甚至没必要运行角色 R2 中的脚本,由于操做 X 已经经过角色 R1 进行了静态受权。于是,脚本不能用来完全拒绝权限。它们只能用来决定是否应当在某个访问检查中考虑特定的任务或角色。仅仅是由于脚本化的任务或角色因为其相应脚本计算为假而被忽略,并不意味着不存在仍会向要测试的操做受权的彻底不一样的任务或角色。Dave McPherson 撰写的白皮书提供了有关运行库如何实现访问检查的很是详细的说明。您能够在“Performance”一节中找到相关内容。对于全部对使用 AzMan 持认真态度的读者,我建议您仔细研究该节的内容。

返回页首返回页首

审核

访问检查的运行库审核是一项重要功能,而且仅当您使用 Active Directory 中的受权管理器存储区时才可用。若是要启用该功能,请右键单击应用程序,选择“Properties”,而后经过“Auditing”选项卡启用该功能。在运行时,用来运行服务器进程的账户很重要:必须授予它“生成审核”特权。内置的账户 Local Service、Network Service 和 SYSTEM 默认状况下都具备该特权。最后,请注意,服务器计算机必须启用对象访问权限审核功能,才能在安全事件日志中捕获这些审核。

您在启用审核以后将看到的现象是,对 AccessCheck 的每一个调用都会产生一个审核条目,该条目中的对象名称是您做为 AccessCheck 的第一个参数传递的任何字符串。操做的易记名称将与客户端的名称一块儿显示在审核中。若是检查成功,则将记录成功访问;不然,您将在该日志中看到失败的访问。您是否可以同时看到成功和失败审核,取决于您经过 Windows 安全策略启用的对象访问审核的级别。

返回页首返回页首

小结

受权管理器是一种用于在 Windows 中构建安全系统的重要工具 。它对 MTS 和 COM+ 所普及的基于角色的安全的思想进行了扩展,它几乎能够由任何服务器应用程序而不只仅是基于 COM 的服务器使用。受权管理器致力于帮助用户将安全逻辑集中到能够存储在 Active Directory 中的简洁安全策略中,而且提供了用于执行访问检查的简单 API。运行库审核还知足了长期须要。

受权管理器具备大量功能,用户在编写安全代码时的部分工做是弄清楚应用程序须要这些功能的哪一个子集。最后,请记住,应该尽量地将事情简单化,以免打开安全漏洞。

相关文章
相关标签/搜索