☰
Windows Classic Samples 之 CustomResourceManager:用 ACL 编辑器接口构建自定义资源 DACL 安全管理器
2026/10/15 2:10:05 网站建设 项目流程
  • 示例工程

【免费下载链接】Windows-classic-samples

This repo contains samples that demonstrate the API used in Windows classic desktop applications.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-classic-samples
点击查看免费下载

导读

CustomResourceManager 是 Windows-classic-samples 仓库中一个围绕ACL 编辑器(ACL Editor / ACLUI)相关接口编写的 C++ 控制台示例,核心目标是演示如何为自定义资源对象实现一套完整的 DACL(Discretionary Access Control List,自主访问控制列表)编辑与有效权限(Effective Access)评估流程。示例将资源建模为一个在线论坛(Forums / Sections / Topics 三层树),你既可以通过系统级 ACL 编辑器界面交互式修改任一资源的访问控制,又能在“有效访问(Effective Access)”页签中验证指定用户或组的实际权限。阅读本文后,你将掌握ISecurityInformation、ISecurityInformation3、IEffectivePermission2、ISecurityObjectTypeInfo四个接口的协作方式,理解自定义权限掩码、泛型权限映射、ACE 继承传播与 AuthZ 访问检查的完整实现路径。

示例概览:用论坛树模拟自定义资源

该样例直接复用了ISecurityInformation系列接口,让 Windows 自带的“安全属性页(ACL Editor)”能够编辑不属于文件系统或注册表的自定义对象的安全描述符(Security Descriptor)。其场景是一个三层论坛资源树(定义见 resource.h 中的ResourceType与ResourceIndices):

层级资源类型
顶层Contoso forumsFORUM(容器)
二层Sports / Movies / HobbiesSECTION(容器)
三层Favorite team、Upcoming events(Sports 下);2012 releases、Classics(Movies 下);Learning to cook、Snowboarding(Hobbies 下)TOPIC(非容器)

每个Resource对象内部保存名称、类型、SDDL 字符串形式的安全描述符以及父/子索引,并通过childIndices列表维护层级关系(见 resource.cpp)。CSecInfo在构造函数(CSecInfo.cpp)中一次性创建全部 10 个资源,随后用一段默认 SDDL 对根对象执行SetSecurity(DACL_SECURITY_INFORMATION, ...),从而让整个树继承初始 DACL。

示例的默认安全描述符(SDDL)为:

O:WDG:BAD:AI(A;CIIO;FA;;;WD)(A;;FA;;;BA)S:AI(AU;SAFACIIO;FA;;;WD)

即:所有者 Everyone、主组 Everyone;DACL 为自动继承(AI),带一个“容器/对象继承 + 仅继承(CIIO)”的对 Everyone 完全控制(FA)ACE 和一个对内置管理员(BA)完全控制的显式 ACE;SACL 同样为自动继承并带有对 Everyone 的成功审核项。

系统要求与构建方式

根据样例自带的 README.md,运行与编译环境如下:

  • 客户端(Client):Windows 8.1
  • 服务器(Server):Windows Server 2012 R2
  • 开发工具:Visual Studio 2013(任意 SKU)及以上版本

构建步骤:

  1. 打开解决方案文件 CustomResourceManager.sln。
  2. 在 Visual Studio 中加载完成后,按F7(Visual Studio 2013 也可用F6),或通过菜单Build → Build Solution编译。

工程配置(CustomResourceManager.vcxproj)中值得注意的两点:

  • 平台工具集为v110,仅提供Debug|Win32与Release|Win32两个配置,使用UNICODE预定义;
  • 链接阶段显式引入authz.lib与aclui.lib,前者支撑 AuthZ 访问检查,后者支撑 ACL 编辑器 UI,这正是“编辑自定义 DACL + 计算有效权限”两条主线的直接依赖。

运行与交互:从菜单到安全属性页

构建完成后,按F5(调试运行)或Ctrl+F5(不带调试运行),或在Debug菜单选择对应选项即可启动。程序启动后打印一个编号菜单(见 main.cpp):

------------------------------------ 1. Contoso forums 2. Sports 3. Favorite team 4. Upcoming events 5. Movies 6. 2012 releases 7. Classics 8. Hobbies 9. Learning to cook 10. Snowboarding 11. Help ------------------------------------ Input a number to view/edit the security descriptor for one of the above:
  • 输入1~10:选中对应资源,随后调用EditSecurity(nullptr, &cSecInfo)(AclUI.h中的入口函数)弹出标准安全属性页,可编辑该资源的 DACL;
  • 输入11:打印 Help 文本,说明资源层级与“可继承 ACE 沿继承层级传播”的行为;
  • 输入其他值:退出程序。

在安全属性页的Effective Access(有效访问)页签中,可以输入或选择某个用户/组,直接验证其对当前资源实际拥有的权限——这一能力正是通过IEffectivePermission2::ComputeEffectivePermissionWithSecondarySecurity实现的(详见下文)。

若EditSecurity返回失败,程序会输出EditSecurity error %d及GetLastError()的错误码。

架构剖析:四个接口与五个源文件

CSecInfo是示例的核心类,它同时实现四个 COM 接口(见 CSecInfo.h):

接口作用
ISecurityInformationACL 编辑器的主体回调:提供对象信息、读写安全描述符、枚举访问权限、映射泛型权限、提供继承类型等
ISecurityInformation3提供资源全名(GetFullResourceName)与提权编辑入口(OpenElevatedEditor)
IEffectivePermission2计算有效权限,支持用户/设备 SID、组与声明(claims)的“假设(what-if)”组合
ISecurityObjectTypeInfo通过GetInheritSource报告每个继承 ACE 的来源(父级或祖父级)

源码按职责拆分为五个文件:

  • main.cpp:控制台菜单与EditSecurity调用;
  • CSecInfo.h / CSecInfo.cpp:类声明、构造函数、继承传播(SetSecurityOfChildren、OrderDacl)、GetInheritSource与有效权限计算;
  • SecurityInformation1.cpp:ISecurityInformation全部方法的实现;
  • resource.h / resource.cpp:Resource资源树模型;
  • utility.h / utility.cpp:ACL 增删、ACE 比较、安全描述符格式转换等底层工具函数。

自定义权限模型:访问掩码、泛型映射与继承类型

权限位定义

示例为“论坛”场景定义了 12 个具体权限位(CSecInfo.h):

#define CREATE_PERM 0x0001 #define READ_PERM 0x0002 #define VOTE_PERM 0x0004 #define UPDATE_OWN_PERM 0x0008 #define UPDATE_OTHERS_PERM 0x0010 #define HIDE_PERM 0x0020 #define SHOW_PERM 0x0040 #define LOCK_PERM 0x0080 #define UNLOCK_PERM 0x0100 #define DESTROY_PERM 0x0200 #define VIEW_PERMS_PERM 0x0400 #define CHANGE_PERMS_PERM 0x0800

并在此基础上叠加出三级“泛型权限”,低层权限被高层权限包含:

#define GENERIC_POST_PERM (CREATE_PERM | READ_PERM | VOTE_PERM | UPDATE_OWN_PERM) #define GENERIC_MOD_PERM (GENERIC_POST_PERM | UPDATE_OTHERS_PERM | HIDE_PERM | SHOW_PERM | LOCK_PERM | UNLOCK_PERM) #define GENERIC_ADMIN_PERM (GENERIC_MOD_PERM | DESTROY_PERM | VIEW_PERMS_PERM | CHANGE_PERMS_PERM)

这三者构成GENERIC_MAPPING ObjectMap(Post→GENERIC_POST_PERM、Moderate→GENERIC_MOD_PERM、Administer→GENERIC_ADMIN_PERM),在MapGeneric回调中通过MapGenericMask(pMask, &ObjectMap)将 ACE 中的泛型访问权展开为具体权限位。

权限列表:g_siForumsAccess

GetAccessRights返回的SI_ACCESS表(CSecInfo.h)是 ACL 编辑器在“编辑”与“添加”权限时展示的条目:

  • 通用条目(显示在“基本权限”区):Administer(GENERIC_ADMIN_PERM)、Moderate(GENERIC_MOD_PERM)、Post(GENERIC_POST_PERM);
  • 高级条目(勾选“显示高级权限”后显示):Create、Read、Vote、Update / edit own content、Update / edit others' content、Hide、Show、Lock、Unlock、Destroy / delete、View permissions、Change permissions,分别对应上述 12 个权限位。

每个条目均带SI_ACCESS_GENERAL/SI_ACCESS_SPECIFIC与INHERIT_FULL(CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE)标记,INHERIT_FULL表示这些 ACE 可同时继承给子容器与子对象。默认新增权限索引为m_DefaultAccess = 0,即默认授予Administer(完全控制)。

继承类型表

容器资源通过GetInheritTypes返回三个继承选项(CSecInfo.h):

显示文本对应 ACE 标志
This object only0(不继承)
This object and children (sections/topics)CONTAINER_INHERIT_ACE
Children (sections/topics) onlyINHERIT_ONLY_ACE | CONTAINER_INHERIT_ACE

样例刻意只使用容器继承(CONTAINER_INHERIT_ACE),以贴合“论坛→版块→话题”的树形结构。非容器(TOPIC)的GetInheritTypes返回E_NOTIMPL,ACL 编辑器将不显示继承控件。

ISecurityInformation 实现要点

GetObjectInformation:控制安全属性页形态

该方法(SecurityInformation1.cpp)组装SI_OBJECT_INFO.dwFlags:

m_dwSIFlags = 0 | SI_ADVANCED // 显示“高级”按钮 | SI_EDIT_PERMS // 允许编辑 DACL | SI_EDIT_OWNER // 高级页允许修改所有者 | SI_PAGE_TITLE // 使用 pszPageTitle 作为基本页标题 | SI_VIEW_ONLY // 实现 ISecurityInformation3 所必需 | SI_EDIT_EFFECTIVE // 启用有效权限编辑 | SI_ENABLE_EDIT_ATTRIBUTE_CONDITION // 启用 ACE 条件编辑 ;

当当前资源是容器(FORUM 或 SECTION)时,追加SI_CONTAINER以显示继承控件。页面标题固定为L"Forums Resource Manager",对象名取自当前资源名。

GetSecurity / SetSecurity:SDDL 与安全描述符的读写

  • GetSecurity(SecurityInformation1.cpp):当fDefault为真时返回默认 SDDL 字符串转换出的安全描述符,否则返回当前资源保存的 SDDL;仅当请求中包含 DACL / OWNER / GROUP 信息时才真正转换。
  • SetSecurity(SecurityInformation1.cpp):ACL 编辑器保存修改时的核心回调,处理逻辑包括:
    1. 将当前资源的 SDDL 转为绝对安全描述符,取出目标 DACL;
    2. 处理SE_DACL_PROTECTED(DACL 上的P标志):若新 SD 带保护位则RemoveAllInheritedAces清掉全部继承 ACE;若此前受保护而用户重新开启继承,则调用SetSecurityOfChildren让父级重新下发可继承 ACE;
    3. AddAllAcesFromAcl(pSourceDacl, &pDestDacl, TRUE)合入编辑后的 ACE(去重),再RemoveExplicitUniqueAces移除已被用户删掉的显式 ACE;
    4. OrderDacl按规范顺序重排 ACE;
    5. 写回绝对 SD 的 DACL/Group/Owner,并转回 SDDL 字符串保存;
    6. 若当前资源是容器,递归SetSecurityOfChildren把新 DACL 传播给全部后代。

GetAccessRights / MapGeneric / GetInheritTypes

  • GetAccessRights:返回g_siForumsAccess表、条目数量与默认权限索引;
  • MapGeneric:清除OBJECT_INHERIT_ACE位(样例不使用对象继承)后调用MapGenericMask展开泛型权限;
  • GetInheritTypes:仅容器返回继承类型表,非容器返回E_NOTIMPL。

继承传播与 ACE 规范化:SetSecurityOfChildren 与 OrderDacl

递归下发可继承 ACE

SetSecurityOfChildren(parentIndex, si, pSD)(CSecInfo.cpp)实现自定义的“继承传播”:

  1. 遍历父资源的所有子资源;
  2. 若子资源 SD 带SE_DACL_PROTECTED,跳过(子对象主动隔离继承);
  3. 提取父 DACL,用GetSizeOfAllInheritableAces统计可继承 ACE 的总大小;
  4. 通过RemoveAllInheritedAces清掉子 DACL 中所有带INHERITED_ACE(ID标志)的旧继承 ACE;
  5. 若子 ACL 为空则按需分配并InitializeAcl;
  6. AddInheritableAcesFromAcl把父级可继承 ACE 复制给子级——复制时置上INHERITED_ACE标志、清除INHERIT_ONLY标志,并在遇到NO_PROPAGATE_INHERIT_ACE时清除子级上的继承标志(见 utility.cpp);
  7. OrderDacl规范化后写回子 SD,并设置SE_DACL_AUTO_INHERITED;
  8. 递归进入子资源,将传播继续延伸到孙级。

DACL 规范排序:OrderDacl

Windows 要求 DACL 中的 ACE 按规范顺序排列(拒绝 ACE 在允许 ACE 之前、显式 ACE 在继承 ACE 之前)。由于继承来源可能跨越两级(父 SECTION、祖父 FORUM),样例需要知道每条继承 ACE 的“代际差(GenerationGap)”,因此OrderDacl(CSecInfo.cpp)借助GetInheritSourceHelper获取PINHERITED_FROM数组,然后分 6 趟完成排序:

趟次选取的 ACE
0显式拒绝(GenerationGap == 0,DENIED)
1显式允许(GenerationGap == 0,ALLOWED)
2来自父级的拒绝(GenerationGap == 1)
3来自父级的允许(GenerationGap == 1)
4来自祖父级的拒绝(GenerationGap == 2)
5来自祖父级的允许(GenerationGap == 2)

根对象(FORUM)没有祖先,直接返回。排序完成后还会做两项健全性检查:ACE 总数一致、ACL 字节占用一致,确保没有 ACE 在重排中丢失。

GetInheritSource:追溯继承来源

ISecurityObjectTypeInfo::GetInheritSource(CSecInfo.cpp)委托给GetInheritSourceHelper(CSecInfo.cpp),对每条带INHERITED_ACE标志的 ACE:

  • 若是 SECTION,检查其是否存在于父 FORUM 的 DACL(用ACEAlreadyInACL比较,且忽略继承标志差异),存在则GenerationGap = 1、AncestorName填父资源名;
  • 若是 TOPIC,先查父 SECTION,不在父级再查祖父 FORUM,从而把GenerationGap标为 1 或 2;
  • 若两边都找不到(成为“孤儿 ACE”),则视为GenerationGap = 0(显式 ACE)。

ACEAlreadyInACL的“forInheritancePurposes”模式会剥离INHERIT_ONLY、NO_PROPAGATE_INHERIT_ACE等仅在父级出现的标志再做逐字节比对(类型、标志、长度、SID 与掩码),并兼容 callback ACE(见 utility.cpp)。

有效权限计算:IEffectivePermission2 与 AuthZ 调用链

安全属性页的Effective Access页签由ComputeEffectivePermissionWithSecondarySecurity(CSecInfo.cpp)驱动,其核心是 AuthZ API 调用链:

  1. AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT, ..., L"SDK Sample Resource Manager", ...)创建资源管理器;
  2. AuthzInitializeContextFromSid分别为用户 SID 与设备 SID 创建 AuthZ 上下文;若存在设备 SID,则用AuthzInitializeCompoundContext合并成复合上下文;
  3. 通过AuthzModifyClaims/AuthzModifySids应用“what-if”的用户/设备声明(claims)与组 SID,支持在编辑器中临时添加组或声明来预测权限变化;
  4. 以MAXIMUM_ALLOWED作为期望访问掩码,构造AUTHZ_ACCESS_REQUEST与AUTHZ_ACCESS_REPLY,调用AuthzAccessCheck对当前资源的安全描述符执行最终访问检查;
  5. 将结果写入PEFFPERM_RESULT_LIST:fEvaluated = true,pGrantedAccessList指向授予的访问掩码,pObjectTypeList指向GUID_NULL的对象类型列表(样例不支持对象 ACE)。

函数开头还会校验参数约束:dwSecurityObjectCount必须为 1、安全对象 ID 必须是SECURITY_OBJECT_ID_OBJECT_SD(样例没有 share 或 CAP 的概念),pSid必须存在,服务器名恒为nullptr。所有句柄在退出前都会通过AuthzFreeResourceManager、AuthzFreeContext等释放,避免泄漏。

小结

CustomResourceManager 以约 1089 行的 C++ 源码完整示范了“自定义资源 + 系统 ACL 编辑器”的集成方式:资源树模型负责描述对象层级,ISecurityInformation家族接口负责把安全描述符、权限位、继承规则呈现给编辑器,SetSecurityOfChildren与OrderDacl负责在保存时完成多级继承传播与 DACL 规范化,而IEffectivePermission2+ AuthZ 则让管理员在不实际修改权限的前提下预览任意用户/组的有效访问。无论你要为业务对象、自定义存储还是管理控制台设计权限面板,这套“SDDL 字符串存储 → 绝对 SD 编辑 → 递归继承 → 有效权限校验”的流水线都极具参考价值。

  • 示例工程

【免费下载链接】Windows-classic-samples

This repo contains samples that demonstrate the API used in Windows classic desktop applications.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-classic-samples
点击查看免费下载
上一篇:ESP32 开发环境搭建五关通关手册:照着做,一次就能装成功
下一篇:Kubernetes iSCSI 持久化卷接入实战:基于 open-iscsi 与 multipathd 的 Pod 存储配置指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询