SAML Azure AD 联合身份 API 参考:从联邦元数据到 Graph API 配置的实战指南
导读
本文以 Anthropic-Cybersecurity-Skills 仓库中 skills/building-identity-federation-with-saml-azure-ad/references/api-reference.md 为核心,系统梳理基于 SAML 2.0 构建 Microsoft Entra ID(原 Azure AD)联合身份时涉及的全部关键 API 端点、元数据字段、XML 命名空间与绑定 URI,并结合仓库中的 SKILL.md、workflows.md、standards.md 及可运行的验证脚本 agent.py 与 process.py 展开源码级解读。读完本文,你将掌握联合元数据的拉取与解析方法、SAML 端点的正确配置方式、SP 元数据必备字段、Graph API 应用注册姿势,以及一套可直接落地的配置校验清单。
一、联合身份背景与 API 参考的定位
在企业混合身份架构中,SAML 2.0 联合使本地 Active Directory(通过 AD FS 或第三方 IdP)认证过的用户能够无缝访问云端资源,而无需在云端维护一套独立凭据。仓库中 SKILL.md 明确指出:联合身份消除了密码同步的顾虑,将认证权威保留在本地,同时将 SSO 扩展到云资源。
该技能共提供四种联合模型,决定着你调用哪些 API、配置哪些端点:
| 模型 | 认证权威 | 典型场景 |
|---|---|---|
| 联合(AD FS) | 本地 AD FS | 法规要求认证留在本地 |
| 托管(PHS) | Azure AD + 密码哈希同步 | 最简单的云认证,无需 AD FS |
| 托管(PTA) | 本地传递认证代理 | 云认证回本地 AD 校验 |
| 第三方联合 | 外部 IdP(Okta、Ping 等) | 多 IdP 环境 |
api-reference.md 正是这套架构下与微软云侧交互的"接线图":无论是拉取 IdP 联合元数据、解析 SP 元数据,还是通过 Graph API 注册应用、执行配置校验,都围绕本文的端点与字段展开。仓库中的 agent.py 将这张接线图完整落地为可执行代码,后续章节会逐一对应。
二、联合元数据 URL:一切配置的起点
联合元数据(Federation Metadata)是一个描述 IdP 端点与能力的 XML 文档,是 IdP 与 SP 双方建立信任的"名片"。
https://login.microsoftonline.com/{tenant-id}/federationmetadata/2007-06/federationmetadata.xml
其中 {tenant-id} 是 Azure AD 租户的全局唯一标识(GUID)。在 agent.py 中,该 URL 被直接用于拉取租户的 SAML 联合元数据:
def fetch_federation_metadata(tenant_id):
"""Fetch Azure AD SAML federation metadata."""
url = f"https://login.microsoftonline.com/{tenant_id}/federationmetadata/2007-06/federationmetadata.xml"
resp = requests.get(url, timeout=15)
resp.raise_for_status()
logger.info("Fetched federation metadata for tenant %s", tenant_id)
return resp.text
值得注意的实现细节:
- 超时控制:
timeout=15防止网络故障导致脚本无限挂起; - 异常传播:
raise_for_status()会在返回非 2xx 状态码(如租户不存在、网络被阻断)时立即抛出异常,避免对无效元数据做无意义的后续解析; - 同样的元数据端点约定也被用在 AD FS 侧——SKILL.md 中配置 Relying Party Trust 时使用的
https://nexus.microsoftonline-p.com/federationmetadata/2007-06/federationmetadata.xml正是微软在线身份平台的元数据地址。
从元数据文档中,可以解析出 IdP 的 entityID、所有 SingleSignOnService 端点及其绑定方式、以及令牌签名证书。这些解析逻辑对应 agent.py:
def parse_metadata(xml_text):
"""Parse SAML federation metadata XML."""
ns = {"md": "urn:oasis:names:tc:SAML:2.0:metadata", "ds": "http://www.w3.org/2000/09/xmldsig#"}
root = ET.fromstring(xml_text)
idp_desc = root.find(".//md:IDPSSODescriptor", ns)
sso_services = []
if idp_desc is not None:
for sso in idp_desc.findall("md:SingleSignOnService", ns):
sso_services.append({"binding": sso.get("Binding"), "location": sso.get("Location")})
certs = []
for cert_elem in root.findall(".//ds:X509Certificate", ns):
if cert_elem.text:
certs.append(cert_elem.text.strip()[:100] + "...")
entity_id = root.get("entityID", "")
return {"entity_id": entity_id, "sso_services": sso_services, "certificates": certs}
这段代码的价值在于:元数据解析是整个联合配置自动化中风险最高的环节——命名空间写错、XPath 路径不对都会静默返回空结果,进而导致校验误判。因此 agent.py 专门将"元数据中没有 SSO 服务"和"元数据中没有签名证书"都判为 critical 级别问题。
三、SAML 2.0 端点一览
联合配置中,Azure AD 作为 IdP 向 SP 暴露三类核心端点,三者共用同一个路径:
| 端点 | URL |
|---|---|
| SSO(POST) | https://login.microsoftonline.com/{tenant}/saml2 |
| SSO(Redirect) | https://login.microsoftonline.com/{tenant}/saml2 |
| Logout | https://login.microsoftonline.com/{tenant}/saml2 |
{tenant} 可以是租户 ID(GUID),也可以是已验证的自定义域名(如 corp.example.com)。从 agent.py 可以看到,客户端在解析出 SSO 服务列表后,会分别检查 HTTP-Redirect 与 HTTP-POST 两种绑定是否可用,其中 HTTP-POST 缺失被记为 medium 严重级别——因为 HTTP-POST 是绝大多数 SAML SP 首选的断言接收方式。
需要强调的一点:同一 URL 承载多种绑定/功能,意味着联合配置中对端点的区分不靠 URL 路径,而靠协议栈中的 Binding 属性与消息类型(AuthnRequest、Response、LogoutRequest)来判定。这也是为什么下文 XML 命名空间与绑定 URI 的精确性如此关键——在 process.py 中,AD FS 元数据校验正是依赖命名空间精确匹配 md:IDPSSODescriptor 下的 ds:X509Certificate 节点来统计证书数量。
四、SP 元数据必备字段
当你的应用作为服务提供商(SP)接入 Azure AD 联合时,需要对外发布 SP 元数据,其中四个字段是必备的:
| 字段 | 说明 |
|---|---|
entityID |
SP 唯一标识符(全局唯一,不能与任何其他 SP 冲突) |
AssertionConsumerService |
ACS URL(POST 绑定),SAML 断言的回传地址 |
NameIDFormat |
emailAddress 或 persistent |
SingleLogoutService |
SLO URL(可选,但建议配置) |
agent.py 中 generate_sp_metadata() 函数完整复现了这一字段结构:
def generate_sp_metadata(entity_id, acs_url, slo_url=None):
"""Generate Service Provider SAML metadata."""
metadata = {
"entityID": entity_id,
"assertionConsumerService": {"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", "location": acs_url},
"nameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
}
if slo_url:
metadata["singleLogoutService"] = {"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", "location": slo_url}
return metadata
实现细节中值得注意的两点:
- 默认
nameIDFormat为emailAddress(即urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress)。这与 SKILL.md 中 SaaS 应用配置步骤"NameID: user.userprincipalname(email 格式)"的约定一致; - SLO 绑定与 ACS 绑定不同:ACS 使用
HTTP-POST回传断言,而 SLO 使用HTTP-Redirect跳转。这是 SAML 常见的最佳实践组合——主动发送用 POST 保证断言完整性,登出跳转用 Redirect 保持简洁。
当 NameID 需要稳定的账号锚点时,应改用 persistent 格式。在 SKILL.md 的 AD FS 声明规则中,正是将 UPN 作为 persistent 格式的 NameID 透传(urn:oasis:names:tc:SAML:2.0:nameid-format:persistent),确保 Azure AD 能够通过稳定的标识符关联到已同步的用户对象。
五、XML 命名空间:解析元数据的钥匙
解析 SAML 元数据与断言时,必须正确声明以下命名空间前缀。 api-reference.md 给出了 Python 解析的命名空间字典:
ns = {
"md": "urn:oasis:names:tc:SAML:2.0:metadata",
"ds": "http://www.w3.org/2000/09/xmldsig#",
"saml": "urn:oasis:names:tc:SAML:2.0:assertion",
}
| 前缀 | 命名空间 URI | 用途 |
|---|---|---|
md |
urn:oasis:names:tc:SAML:2.0:metadata |
元数据文档结构(EntityDescriptor、IDPSSODescriptor、SingleSignOnService 等) |
ds |
http://www.w3.org/2000/09/xmldsig# |
XML 数字签名(X509Certificate 等证书节点) |
saml |
urn:oasis:names:tc:SAML:2.0:assertion |
SAML 断言结构(Assertion、Subject、AttributeStatement 等) |
仓库两个脚本各自印证了这些命名空间的使用方式:
- agent.py 用
md+ds解析 Azure AD 元数据中的 SSO 服务与证书; - process.py 用同样的前缀组合定位 AD FS 元数据中的
IDPSSODescriptor/KeyDescriptor/KeyInfo/X509Data/X509Certificate节点。
一个常见的坑:命名空间前缀名称可以随意(md/md1/m 均可),但 URI 必须逐字符精确匹配。如果 URI 拼写错误或缺失,findall 将返回空列表,导致"元数据看起来正常但解析结果为空"的隐性故障。这也是 process.py 捕获 Exception 并返回 parse_error 的原因——把解析异常显性暴露出来,而不是静默吞掉。
六、SAML 绑定 URI 速查
SAML 2.0 定义了多种消息传输绑定,每种绑定有标准的命名空间 URI,在元数据与端点配置中必须按此精确填写:
| 绑定 | URI |
|---|---|
| HTTP-POST | urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST |
| HTTP-Redirect | urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect |
| SOAP | urn:oasis:names:tc:SAML:2.0:bindings:SOAP |
此外,standards.md 还提到 SAML 2.0 存在 HTTP Artifact 绑定,但 Azure AD 联合场景中最常使用的是前两种。
在实战中的典型用法:
- SP 元数据中的 ACS:声明为
HTTP-POST绑定(见上文generate_sp_metadata),表示 SP 期望以 POST 方式接收 SAML Response; - SLO 端点:声明为
HTTP-Redirect绑定; - 校验逻辑:agent.py 在解析 IdP 元数据后,检查
sso_services中是否存在HTTP-POST与HTTP-Redirect绑定,以此判断 IdP 的能力是否满足 SP 的需求——这是将绑定 URI 用于程序化校验的直接证据。
七、Azure AD Graph API:应用注册与联合配置
除了元数据层面的交互,联合配置还涉及 Microsoft Graph API 的调用。 api-reference.md 给出了创建服务主体的示例:
POST https://graph.microsoft.com/v1.0/servicePrincipals
Authorization: Bearer TOKEN
{
"appId": "app-id",
"preferredSingleSignOnMode": "saml",
"loginUrl": "https://app.example.com/login"
}
关键字段含义:
| 字段 | 说明 |
|---|---|
appId |
已注册应用(App Registration)的应用程序 ID |
preferredSingleSignOnMode |
设为 saml 表明该服务主体使用 SAML SSO 模式 |
loginUrl |
应用的登录 URL,用于 IdP 发起的 SSO 流程 |
在仓库中,Graph API 的调用被 process.py 的 FederationAuditor 类系统化落地。其认证与调用链路包括:
- MSAL 客户端凭据获取令牌(process.py):使用
msal.ConfidentialClientApplication以客户端 ID + 客户端密钥换取https://graph.microsoft.com/.default作用域下的访问令牌; - 域列表查询(process.py):
GET /v1.0/domains,提取每个域的authenticationType(Federated/Managed等)、验证状态与是否默认域; - 联合配置查询(process.py):
GET /v1.0/domains/{domain_id}/federationConfiguration,返回issuerUri、passiveSignInUri、signingCertificate等关键配置;404 时返回None(表示该域不是联合域); - 登录日志审计(process.py):
GET /v1.0/auditLogs/signIns?$filter=userPrincipalName endswith '{domain}',用于验证联合用户的真实登录行为。
运行审计器所需的最小 Graph 权限如下(process.py):
- Domain.Read.All
- AuditLog.Read.All
- Directory.Read.All
依赖安装与使用方式:
pip install msal requests cryptography
auditor = FederationAuditor(tenant_id, client_id, client_secret)
report = auditor.generate_federation_audit_report()
print(json.dumps(report, indent=2))
审计报告会为每个联合域输出 issuer_uri、sign_in_url、certificate_health(含 days_until_expiry、needs_renewal 标记),并将证书过期(Critical)与 30 天内到期(High)写入 findings 列表——这正对应 api-reference.md 校验清单中"Certificate present"为 Critical 级别的实践。证书到期检测通过 check_certificate_expiry 对 Base64 编码的证书做 DER 解码后读取 not_valid_after_utc 实现。
八、配置校验清单:严重级别与实操对照
api-reference.md 定义的四项校验,在仓库脚本中均有对应实现,并叠加了 SKILL.md 验证清单中的安全项:
| 校验项 | 严重级别 | 代码实现 |
|---|---|---|
| ACS URL 使用 HTTPS | High | agent.py#L61-L62:检查 assertionConsumerService.location 是否以 https:// 开头 |
| 证书存在 | Critical | agent.py#L59-L60:IdP 元数据中无证书判 Critical;process.py 进一步校验到期时间 |
| HTTP-POST 绑定可用 | Medium | agent.py#L63-L66:遍历 SSO 服务绑定判断 |
| NameID 格式已配置 | Medium | agent.py#L47:SP 元数据中显式声明 nameIDFormat |
执行入口为 agent.py 的 main():
python agent.py --tenant-id <TENANT_ID> --sp-entity-id <SP_ENTITY_ID> --acs-url <ACS_URL> [--slo-url <SLO_URL>] [--output saml_report.json]
完整参数说明:
| 参数 | 必填 | 说明 |
|---|---|---|
--tenant-id |
是 | Azure AD 租户 ID |
--sp-entity-id |
是 | 服务提供方实体 ID |
--acs-url |
是 | Assertion Consumer Service URL |
--slo-url |
否 | Single Logout URL |
--output |
否 | 报告输出路径,默认 saml_report.json |
执行后会拉取租户元数据 → 生成 SP 元数据 → 执行校验 → 输出 JSON 报告,报告包含 idp_metadata、sp_configuration 与 validation 三个区块,并以 SAML REPORT: VALID/INVALID, N findings 的形式打印摘要。
从源码结构看,validate_configuration 的判定逻辑是:只要不存在 critical 级别问题即判定 valid,但 High/Medium 问题仍会完整记录在 findings 中——这意味着"校验通过"不代表"配置完美",实施时应把 High/Medium 项也逐一关闭。这一设计与 SKILL.md 验证清单相互呼应:证书自动轮换、智能锁定、外网锁定策略、AD FS 健康与证书到期监控、托管认证回退方案等,都属于联合上线前的安全加固闭环。
九、故障场景:联合中断时的 API 侧处置
结合 workflows.md 的故障切换工作流,联合架构遇到 AD FS 中断时,API 侧的处置路径与本文端点/校验体系直接相关:
- 状态判定:通过 process.py 的
validate_adfs_metadata探测 AD FS 元数据端点是否可达,获取entity_id、certificate_count、metadata_size作为健康证据; - 回退方案 A(暂态切换):启用密码哈希同步作为备份,利用 Azure AD 分阶段发布(staged rollout)将部分用户组切到托管认证;
- 回退方案 B(紧急转换):执行
Convert-MgDomainToManaged(对应PUT /v1.0/domains/{domainId}/federationConfiguration类操作的反向),将所有用户切至 Azure AD 直接认证; - 恢复验证:AD FS 恢复后重建联合信任,重新将域转回 federated,并通过 process.py 的登录日志接口确认
authenticationProtocol已回到联合认证路径。
证书轮换同样是 API 侧的常见操作:自动轮换模式下,AD FS 在证书到期前 20 天生成新证书并作为辅助证书加入,Azure AD 通过元数据刷新自动获取(workflows.md);手动轮换则需依次执行 Set-AdfsCertificate(添加为辅助)→ Update-MgDomainFederationConfiguration 更新 Azure AD → 等待 24-48 小时复制 → 提升为主证书 → 移除旧证书。整个轮换窗口的健康状态都可以用 process.py 的 generate_federation_audit_report() 持续监控。
十、速查与合规映射
最后,将 API 参考中所有关键值汇总为速查表,便于实际配置时对照使用:
| 对象 | 关键值 |
|---|---|
| 元数据 URL | https://login.microsoftonline.com/{tenant-id}/federationmetadata/2007-06/federationmetadata.xml |
| SSO/Logout 端点 | https://login.microsoftonline.com/{tenant}/saml2 |
| 元数据命名空间 | urn:oasis:names:tc:SAML:2.0:metadata |
| 签名命名空间 | http://www.w3.org/2000/09/xmldsig# |
| 断言命名空间 | urn:oasis:names:tc:SAML:2.0:assertion |
| HTTP-POST 绑定 | urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST |
| HTTP-Redirect 绑定 | urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect |
| SOAP 绑定 | urn:oasis:names:tc:SAML:2.0:bindings:SOAP |
| emailAddress NameID | urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress |
| persistent NameID | urn:oasis:names:tc:SAML:2.0:nameid-format:persistent |
在合规层面,standards.md 给出了本架构对应的合规映射:
- NIST SP 800-63C:FAL1(Bearer 断言直接呈现)、FAL2(Bearer 断言附加安全)、FAL3(持有者密钥断言);
- FedRAMP:IA-2/IA-5/IA-8,跨组织访问要求联合;
- ISO 27001:2022:A.5.16 身份管理、A.5.17 认证信息、A.8.5 安全认证。
这意味着上述 API 端点与校验项不仅是工程配置,也是满足审计要求的可验证证据——建议在实施完成后,将 agent.py 的校验报告与 process.py 的审计报告一并归档,形成配置基线。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0632
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00