使用Python获取Azure应用注册所有者遇问题,求技术指导
问题分析与解决方案
核心问题:误解appOwnerOrganizationId字段含义
你的代码中错误将appOwnerOrganizationId当作用户/所有者的Object ID,但这个字段实际代表的是应用注册所属的Azure AD租户ID,并非具体的用户或所有者ID,这是导致无法获取正确所有者信息的根本原因。
正确的所有者获取方式
要获取应用注册(或服务主体)的实际所有者,需要调用Microsoft Graph的owners导航属性接口,以下是修正后的实现:
1. 修正权限配置
确保你的服务主体已被授予以下Microsoft Graph权限(需管理员同意):
Application.Read.All:读取应用注册信息Directory.Read.All:读取目录对象(所有者信息)
2. 修正代码逻辑
import logging import requests import datetime from datetime import timezone from dateutil.parser import parse from azure.identity import ClientSecretCredential from msgraph.core import GraphClient def get_application_owners(graph_client, app_object_id): """ 获取应用注册的所有所有者 """ try: # 调用owners导航属性获取所有者列表 response = graph_client.get(f"/applications/{app_object_id}/owners") response.raise_for_status() owners = response.json().get('value', []) # 提取所有者名称(支持用户、组、服务主体) owner_names = [] for owner in owners: if 'userPrincipalName' in owner: owner_names.append(owner.get('displayName') or owner.get('userPrincipalName')) elif 'displayName' in owner: owner_names.append(owner.get('displayName')) else: owner_names.append('Unknown Owner') return ', '.join(owner_names) if owner_names else 'N/A' except Exception as e: logging.error(f"获取所有者失败: {str(e)}") return 'N/A' # 初始化凭证和Graph客户端 credential = ClientSecretCredential(tenant_id, client_id, client_secret) graph_client = GraphClient(credential=credential) # 获取所有服务主体并关联应用注册信息 expiring_secrets = [] expiry_threshold = datetime.datetime.now(timezone.utc) # 替换为你的阈值时间 # 先获取所有应用注册,关联appId和objectId apps_response = graph_client.get("/applications") apps = apps_response.json().get('value', []) app_id_to_object_id = {app['appId']: app['id'] for app in apps} # 遍历服务主体 sp_response = graph_client.get("/servicePrincipals") service_principals = sp_response.json().get('value', []) for sp in service_principals: app_id = sp.get('appId') app_object_id = app_id_to_object_id.get(app_id) owner_name = 'N/A' if app_object_id: owner_name = get_application_owners(graph_client, app_object_id) # 处理密钥过期逻辑 secrets = sp.get('passwordCredentials', []) for secret in secrets: end_date_str = secret.get('endDateTime') if end_date_str: end_date = parse(end_date_str).replace(tzinfo=timezone.utc) if end_date <= expiry_threshold: human_friendly_date = end_date.strftime('%Y-%m-%d %H:%M:%S %Z') expiring_secrets.append({ 'displayName': sp.get('displayName'), 'secretId': secret.get('keyId'), 'expiryDate': human_friendly_date, 'owner': owner_name })
关键修正点说明
- 区分应用注册与服务主体:应用注册(
applications)是定义应用元数据的对象,服务主体(servicePrincipals)是应用在租户中的实例,两者通过appId关联。 - 使用
owners导航属性:通过/applications/{objectId}/owners或/servicePrincipals/{objectId}/owners获取实际所有者,这些所有者可以是用户、组或其他服务主体。 - 处理多所有者场景:一个应用注册可能有多个所有者,需遍历返回的列表并提取名称。
- 时区处理:确保日期比较时使用UTC时区,避免时区偏差导致的判断错误。
内容的提问来源于stack exchange,提问作者user3307437
相关产品推荐
相关产品推荐

