ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从 Microsoft Entra ID 导入组织数据到 Backstage 软件目录:msgraph 目录插件完全指南

从 Microsoft Entra ID 导入组织数据到 Backstage 软件目录:msgraph 目录插件完全指南 从 Microsoft Entra ID 导入组织数据到 Backstage 软件目录msgraph 目录插件完全指南【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本指南基于 Backstage 仓库中的docs/integrations/azure/org.md编写系统讲解如何通过backstage/plugin-catalog-backend-module-msgraph及增量版-msgraph-incremental插件借助 Microsoft Graph API 将 Microsoft Entra ID原 Azure AD租户中的用户、群组与组织信息批量导入 Backstage 软件目录。读完本文你将掌握标准与增量两种导入方式的选择与安装、四种 Microsoft Graph 认证方式的配置、用户/群组的过滤与搜索策略包括path参数、userGroupMember、loadPhotos等细节、利用自定义 Transformer 深度定制实体映射的方法以及常见故障的排查路径。功能概述Backstage 的 Catalog软件目录支持直接从 Microsoft Entra ID 租户中摄取组织数据——用户与团队。该能力由backstage/plugin-catalog-backend-module-msgraph插件提供其核心是MicrosoftGraphOrgEntityProvider实现位于 MicrosoftGraphOrgEntityProvider.ts。它作为 Catalog 的 EntityProvider 被注册到catalogProcessingExtensionPoint按schedule配置周期性调用 Microsoft Graph API将User、Group以及代表整个组织的根Group实体写入 Catalog。从源码角度看整个摄取流程分为三条主链路见 org.ts读取通过MicrosoftGraphClient分页拉取用户与群组数据转换由默认的defaultUserTransformer、defaultGroupTransformer、defaultOrganizationTransformerdefaultTransformers.ts将 Graph 对象映射为UserEntity/GroupEntity关系构建buildOrgHierarchy()负责双向修正spec.parent与spec.childrenbuildMemberOf()为每个用户补齐传递性transitive的群组归属关系最终写入user.spec.memberOf。摄取的实体还会被打上graph.microsoft.com/tenant-id、graph.microsoft.com/group-id、graph.microsoft.com/user-id与microsoft.com/email等注解定义见 constants.ts这些注解是后续按 Graph 对象 ID 回查、去重与关联的关键标识。安装与基础配置该插件默认不会随 Backstage 安装需要手动添加到后端包中。# 在 Backstage 根目录执行 yarn --cwd packages/backend add backstage/plugin-catalog-backend-module-msgraph接着在app-config.yaml中添加基础配置catalog: providers: microsoftGraphOrg: default: tenantId: ${AZURE_TENANT_ID} user: filter: userType eq member group: filter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:ceqUnified) schedule: frequency: PT1H timeout: PT50M最后在后端入口注册该模块packages/backend/src/index.tsbackend.add(import(backstage/plugin-catalog-backend)); backend.add(import(backstage/plugin-catalog-backend-module-msgraph));注意对于大型组织首次全量导入耗时可能很长请谨慎设置过低的frequency/timeout避免任务超时中断。配置结构解析从配置解析源码 config.ts 可以看到catalog.providers.microsoftGraphOrg下可以声明多个 provider每个以唯一 id 为键且存在两种写法多 provider 写法推荐microsoftGraphOrg下每个键是一个 provider id如default、production单 provider 简化写法直接在microsoftGraphOrg下平铺tenantId、clientId等字段源码通过providersConfig.has(clientId)判断此时内部使用固定的 provider iddefault。关键配置项对应MicrosoftGraphProviderConfig类型与默认值如下配置项说明默认值targetMicrosoft Graph 基础 URL末尾斜杠会被自动去除https://graph.microsoft.com/v1.0authority认证机构地址如https://login.microsoftonline.com无使用 Azure Identity 默认tenantId目标租户 ID必填无clientId/clientSecret应用注册的客户端凭据二者必须成对出现否则配置解析直接报错无user.filter用户过滤条件OData$filter无user.select/user.expand查询时拉取的字段 /$expand参数无user.path用户端点路径usersuser.loadPhotos是否加载用户头像trueuserGroupMember.filter/.search按群组成员导入用户时的群组过滤/搜索条件无userGroupMember.path群组成员端点路径无group.filter/group.search群组过滤 / 搜索条件无group.select/group.expand群组字段 /$expand无group.path群组端点路径groupsgroup.includeSubGroups是否一并导入匹配群组的子群组falsequeryMode查询模式basic或advanced进阶查询能力basicschedule.frequency/schedule.timeout定时任务频率与超时ISO 8601 时长无需显式配置配置解析层还内置了若干强校验config.tsuserFilter与userGroupMemberFilter互斥定义了userFilter时不能再设置userGroupMemberSearch/userGroupMemberPathqueryMode只能是basic或advancedclientId与clientSecret必须成对。配置写错会在启动阶段直接抛出明确错误便于及早发现问题。大型租户的增量导入方案对于数据量极大、无法一次性把全量数据集载入内存的 Entra ID 租户backstage/plugin-catalog-backend-module-msgraph-incremental提供了内存友好的替代方案它一页一页地处理用户与群组并把odata.nextLink游标持久化Pod 重启后可从上次完成的页继续导入无需从头开始。安装两个依赖yarn --cwd packages/backend add backstage/plugin-catalog-backend-module-incremental-ingestion yarn --cwd packages/backend add backstage/plugin-catalog-backend-module-msgraph-incremental注册到后端backend.add(import(backstage/plugin-catalog-backend)); backend.add( import(backstage/plugin-catalog-backend-module-incremental-ingestion), ); backend.add( import(backstage/plugin-catalog-backend-module-msgraph-incremental), );增量 provider 复用与标准模块相同的catalog.providers.microsoftGraphOrg配置但不支持userGroupMember*与groupIncludeSubGroups两类选项若确实需要这些能力请回退使用MicrosoftGraphOrgEntityProvider。从源码实现MicrosoftGraphIncrementalEntityProvider.ts可以看到增量方案的设计细节定义了MSGraphCursor游标类型{ phase: users | groups; nextLink?: string }其中nextLink直接保存 Graph API 返回的odata.nextLink天然编码了续传所需的全部状态用户阶段每页拉取 999 条USER_PAGE_SIZE 999群组阶段每页仅拉取 100 条GROUP_PAGE_SIZE 100因为群组阶段还要为每页上的每个群组获取成员较小的页尺寸可让每个突发请求控制在时间预算内实体名超过 Backstage 63 字符上限时如日历/预订类账号的 UPN会截断到 54 字符并追加 8 位 SHA-1 哈希以保证唯一性通过withLocations()以msgraph:providerId/uid的形式为实体打上backstage.io/location注解确保实体可被追踪与去重。两种方案对比如下维度MicrosoftGraphOrgEntityProviderIncremental provider内存占用全量数据载入 RAM一次仅处理一页重启后续传从头开始从游标续传userGroupMember*选项支持不支持groupIncludeSubGroups支持不支持适合大型租户否是使用 Microsoft Graph 认证本地开发环境本地开发时推荐安装 Azure CLI 或 Azure PowerShell 并完成登录也可以使用带 Azure 扩展的 VSCode需额外安装azure/identity-vscode。配置好这些之后插件会通过 Azure Identity 的默认凭据链自动完成 Graph API 认证无需配置任何凭据或授予特殊权限。如果上述方式都不可行则需创建 App Registration。应用注册App Registration如果其他认证方式均不可用可在 Azure Portal 中创建应用注册。默认情况下插件需要以下 Microsoft Graph 的应用程序权限Application permissions非 Delegated 委托权限GroupMember.Read.AllUser.Read.All如果组织要求对这些权限进行管理员同意Admin Consent需要提前完成授权流程。使用 ClientId/ClientSecret 认证时既可以设置AZURE_TENANT_ID、AZURE_CLIENT_ID、AZURE_CLIENT_SECRET环境变量也可以在配置中直接指定microsoftGraphOrg: default: # ... clientId: 9ef1aac6-b454-4e69-9cf5-7199df049281 clientSecret: REDACTED示例中的clientId仅作格式演示请替换为你自己的应用注册值。也可以使用证书而非客户端密钥认证此时设置AZURE_TENANT_ID、AZURE_CLIENT_ID、AZURE_CLIENT_CERTIFICATE_PATH三个环境变量即可。托管标识Managed Identity如果部署到支持托管标识且已配置标识的 Azure 资源如 Azure App Services、Azure Container Apps插件会自动拾取托管标识无需额外配置。当应用拥有多个托管标识时可能需要设置AZURE_CLIENT_ID环境变量来指定 Azure Identity 应使用的标识。为托管标识授予与上面应用注册一节相同的权限即可。过滤导入的用户与群组默认情况下插件会导入目录中**所有已启用enabled**的用户与所有群组已禁用的用户账号accountEnabled eq false会被自动排除。你还可以通过 Graph 的$filter过滤查询参数与$search搜索查询参数进一步定制。任何自定义的user.filter都会以and方式与基础过滤条件accountEnabled eq true组合。群组过滤与搜索通过配置search或filter可以获取更小的群组集合如果同时提供filter和search则群组必须同时满足两者才会被导入。microsoftGraphOrg: providerId: group: filter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:ceqUnified) search: description:One AND (displayName:Video OR displayName:Drive)如果不只想导入匹配search和/或filter的群组还想一并导入这些群组的成员群组可开启includeSubGroupsmicrosoftGraphOrg: providerId: group: filter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:ceqUnified) search: description:One AND (displayName:Video OR displayName:Drive) includeSubGroups: true除这些群组之外插件还会为你的组织额外创建一个根群组所有导入的群组都将是它的子群组对应defaultOrganizationTransformer创建的type: root的 Group 实体。用户过滤与按群组成员导入导入用户有两种模式。第一种是导入所有匹配filter的用户对象基础过滤accountEnabled eq true会自动应用并与自定义过滤条件组合microsoftGraphOrg: providerId: user: filter: userType eq member第二种是导入特定群组的成员用户对于每个匹配search与filter查询的群组其每位成员都会被导入。注意只导入直接成员不导入传递性成员transient users。microsoftGraphOrg: providerId: userGroupMember: filter: displayName eq Backstage Users search: description:One AND (displayName:Video OR displayName:Drive)需要说明的是虽然userGroupMember模式只导入直接成员但在关系构建阶段buildMemberOf()org.ts会结合已有群组层级为每个用户算出传递性的memberOf归属链——即用户所属群组、以及该群组的所有上级群组都会被写入user.spec.memberOf。使用path参数默认情况下 provider 通过 msgraph 的/group与/user端点获取群组和用户但可以通过设置path配置改用其他端点。任何包含/microsoft.graph.group的端点都会返回正确的群组对象类型任何包含/microsoft.graph.user的端点都会返回正确的用户对象类型。示例给定如下组织结构可以使用path参数获取someRootGroup在所有层级上的用户与群组成员配置如下microsoftGraphOrg: providerId: group: path: /groups/{someRootGroup id}/transitiveMembers/microsoft.graph.group user: path: /groups/{someRootGroup id}/transitiveMembers/microsoft.graph.user使用transitiveMembers端点将返回someRootGroup在所有层级上的全部用户与群组成员。用户头像User photos默认情况下插件会拉取用户头像并添加到每个用户实体上。对于超大型组织这可能不可行——拉取头像会耗费非常长的时间可通过将loadPhotos设为false关闭microsoftGraphOrg: providerId: user: filter: ... loadPhotos: false如果使用userGroupMember模式loadPhotos的配置仍应放在user:下同时省略search和filtersmicrosoftGraphOrg: providerId: user: loadPhotos: false userGroupMember: filter: displayName eq Backstage Users search: description:One AND (displayName:Video OR displayName:Drive)自定义实体转换Transformers摄取的实体可以通过自定义 transformer 进行定制。它们既可以完全替换内置逻辑也可以调用默认 transformerdefaultGroupTransformer、defaultUserTransformer、defaultOrganizationTransformer实现见 defaultTransformers.ts后再做微调。返回undefined即可将对应实体从 Backstage 中排除。当使用自定义 transformer 时你可能还想调整 Graph 查询返回的数据可通过以下配置项定制查询microsoftGraphOrg: providerId: user: expand: manager group: expand: member select: [id, displayName, description]动态配置缩放Provider Config Transformer动态配置缩放允许 msgraph catalog 插件在运行时调整设置而无需重新部署。这对需要根据实时事件或变化条件更新配置的场景非常有用例如动态调整同步调度、过滤条件与搜索参数以优化性能与响应性。注意调整那些并非每次定时摄取都会使用的字段如id、schedule不会产生任何效果。警告在运行中动态变更配置可能引入意外后果如系统不稳定与配置错误。请仔细审查你的 transformer确保其行为符合预期典型用例过滤条件缩放动态调整userGroupMember、groupFilter等过滤条件搜索参数调整随时修改groupSearch、userSelect等搜索参数。注册自定义 TransformerTransformer 通过扩展microsoftGraphOrgEntityProviderTransformExtensionPoint配置。该扩展点定义于 catalogModuleMicrosoftGraphOrgEntityProvider.ts提供setUserTransformer、setGroupTransformer、setOrganizationTransformer、setProviderConfigTransformer四个方法均可传入单个函数或按 provider id 分组的Recordstring, Transformer且每种 transformer只能被设置一次重复设置会抛出错误。在packages/backend/src/index.ts中注册示例import { createBackendModule } from backstage/backend-plugin-api; import { microsoftGraphOrgEntityProviderTransformExtensionPoint } from backstage/plugin-catalog-backend-module-msgraph/alpha; import { myUserTransformer, myGroupTransformer, myOrganizationTransformer, myProviderConfigTransformer, } from ./transformers; backend.add( createBackendModule({ pluginId: catalog, moduleId: microsoft-graph-extensions, register(env) { env.registerInit({ deps: { microsoftGraphTransformers: microsoftGraphOrgEntityProviderTransformExtensionPoint, }, async init({ microsoftGraphTransformers }) { microsoftGraphTransformers.setUserTransformer(myUserTransformer); microsoftGraphTransformers.setGroupTransformer(myGroupTransformer); microsoftGraphTransformers.setOrganizationTransformer( myOrganizationTransformer, ); microsoftGraphTransformers.setProviderConfigTransformer( myProviderConfigTransformer, ); }, }); }, }), );myUserTransformer、myGroupTransformer、myOrganizationTransformer、myProviderConfigTransformer这几个函数来自下面章节的示例。Transformer 示例下面给出每种 transformer 的示例。建议在packages/backend/src下创建transformers.ts文件存放这些函数。首先建立文件的基础结构为每种 transformer 提供直接透传默认 transformer 的函数import * as MicrosoftGraph from microsoft/microsoft-graph-types; import { defaultGroupTransformer, defaultUserTransformer, defaultOrganizationTransformer, microsoftGraphOrgEntityProviderTransformExtensionPoint, MicrosoftGraphProviderConfig, } from backstage/plugin-catalog-backend-module-msgraph; import { GroupEntity, UserEntity } from backstage/catalog-model; import { createBackendModule } from backstage/backend-plugin-api; // 群组 transformer转换从 MS Graph 导入的 Group export async function myGroupTransformer( group: MicrosoftGraph.Group, groupPhoto?: string, ): PromiseGroupEntity | undefined { const backstageGroup await defaultGroupTransformer(group, groupPhoto); return backstageGroup; } // 用户 transformer转换从 MS Graph 导入的 User export async function myUserTransformer( graphUser: MicrosoftGraph.User, userPhoto?: string, ): PromiseUserEntity | undefined { const backstageUser await defaultUserTransformer(graphUser, userPhoto); return backstageUser; } // 组织 transformer将根 MS Graph Organization 转换为 Group export async function myOrganizationTransformer( graphOrganization: MicrosoftGraph.Organization, ): PromiseGroupEntity | undefined { const backstageOrg await defaultOrganizationTransformer(graphOrganization); return backstageOrg; } // Provider 配置 transformer支持修改插件配置 export async function myProviderConfigTransformer( provider: MicrosoftGraphProviderConfig, ): PromiseMicrosoftGraphProviderConfig { return provider; } // 将这些函数包装进一个 Module便于注入 Catalog 插件 export default createBackendModule({ pluginId: catalog, moduleId: msgraph-org, register(reg) { reg.registerInit({ deps: { microsoftGraphTransformers: microsoftGraphOrgEntityProviderTransformExtensionPoint, }, async init({ microsoftGraphTransformers }) { microsoftGraphTransformers.setUserTransformer(myUserTransformer); microsoftGraphTransformers.setGroupTransformer(myGroupTransformer); microsoftGraphTransformers.setOrganizationTransformer( myOrganizationTransformer, ); microsoftGraphTransformers.setProviderConfigTransformer( myProviderConfigTransformer, ); }, }); }, });群组 Transformer完全替换默认逻辑这个示例完全移除默认逻辑替换为自定义实现——假设所有群组名都以组织单元前缀命名如Engineering - Team A我们希望丢弃组织单元前缀、并把它用作命名空间export async function myGroupTransformer( group: MicrosoftGraph.Group, groupPhoto?: string, ): PromiseGroupEntity | undefined { // 所有群组都以组织单元前缀命名Engineering - Team A // 我们丢弃群组名中的组织单元并将其用作命名空间 const groupNameArr group.displayName.split( - ); const displayName groupNameArr[1]; // 用连字符替换空格并转为小写标准化 name 与 namespace const namespace groupNameArr[0].replace( , -).toLowerCase(); const groupName groupNameArr[1].replace( , -).toLowerCase(); return { apiVersion: backstage.io/v1alpha1, kind: Group, metadata: { name: groupName, description: group.description, annotations: {}, }, spec: { type: team, displayName: displayName, email: group.mail, children: [], }, }; }用户 Transformer复用内置逻辑并微调这个示例调用内置逻辑同时修改用户名并设置描述export async function myUserTransformer( graphUser: MicrosoftGraph.User, userPhoto?: string, ): PromiseUserEntity | undefined { const backstageUser await defaultUserTransformer(graphUser, userPhoto); // 确保默认 transformer 返回了实体 if (backstageUser) { // 更新描述表明该实体来源 backstageUser.metadata.description Loaded from Microsoft Entra ID via MyCustomUserTransformer; // 默认 transformer 会把用户名设为邮箱地址并替换非法字符user_domain.com // 这里改为邮箱的本地部分去掉域名并转小写 const newName backstageUser.metadata.name.split(_)[0].toLowerCase(); backstageUser.metadata.name newName; return backstageUser; } return undefined; }组织 Transformer移除组织根群组这个示例通过返回undefined完全移除组织群组export async function myOrganizationTransformer( graphOrganization: MicrosoftGraph.Organization, ): PromiseGroupEntity | undefined { // 组织 transformer 会创建一个群组作为群组关系树的根基 // 我们不需要创建它因此返回 undefined 而不是实体 return undefined; }Provider 配置 Transformer动态扩展过滤条件这个示例扩展群组过滤条件确保azure-group-a始终被包含export async function myProviderConfigTransformer( provider: MicrosoftGraphProviderConfig, ): PromiseMicrosoftGraphProviderConfig { // 配置文件中的过滤条件依赖一个偶发导致该重要群组导入失败的属性 // 确保该群组总能被过滤条件发现 if (!provider.groupFilter?.includes(azure-group-a)) { provider.groupFilter ${provider.groupFilter} or displayName eq azure-group-a; } return provider; }最后把新模块添加到后端即可// 你的文件里会有更多内容 const backend createBackend(); // ... backend.add(import(./extensions/transformers)); // ... backend.start();需要留意示例中的myProviderConfigTransformer修改了provider.groupFilter对应配置项group.filter这属于每次定时摄取都会使用的字段因此动态调整能生效而id、schedule等字段的调整会被忽略catalogModuleMicrosoftGraphOrgEntityProvider.ts 中扩展点文档亦有同样说明。故障排查没有数据导入首先检查日志中是否出现Reading msgraph users and groups消息。如果看不到这条日志请检查 provider 是否已注册、schedule是否合法有效。如果看到Read 0 msgraph users and 0 msgraph groups请检查search与filter参数。如果看到了开始消息Reading msgraph users and groups但没有结束消息Read X msgraph users and Y msgraph groups很可能是数据量过大导致任务耗时过长。默认行为是导入所有用户与群组这往往超出实际需要。可以尝试导入更小的数据集例如filter: displayName eq John Smith。认证 / Token 错误参见 Microsoft 官方文档《Troubleshooting Azure Identity Authentication Issues》https://aka.ms/azsdk/js/identity/troubleshoot。读取用户时报错Authorization_RequestDenied - Insufficient privileges确保已为应用注册或托管标识授予全部所需权限确保是Application权限而非Delegated权限如果组织配置了管理员同意要求请确保已为应用程序权限授予管理员同意如果群组查询返回的是 Microsoft Teams 群组可能需要额外授予权限如Team.ReadBasic.All、TeamMember.Read.All如果添加了额外的select或expand字段这些字段可能需要额外授予相应权限。总结从 Microsoft Entra ID 同步组织数据是搭建 Backstage 软件目录人员与团队维度的基础能力。标准模块MicrosoftGraphOrgEntityProvider适合中小规模组织通过丰富的过滤、搜索、path、loadPhotos配置即可精确控制导入范围对于超大型租户msgraph-incremental提供按页处理与游标续传的内存友好方案。而四类自定义 Transformer用户、群组、组织、Provider 配置配合扩展点机制让实体映射可以完全贴合企业自身的组织模型。可进一步参考仓库内的相关文档与源码微软 Graph 集成索引见 docs/integrations/azure/index.md插件实现与测试见 plugins/catalog-backend-module-msgraph 与 plugins/catalog-backend-module-msgraph-incremental。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表