OpenID Connect Discovery 1.0 深度解析:OP 的“自我介绍”和动态配置检索
Source: Dev.to
请提供您希望翻译的完整文本内容,我将按照要求保留源链接、格式和技术术语,仅翻译正文部分。谢谢!
Introduction
您可能每天都在使用 OIDC(OpenAI Connect) 将 Google 登录或其他身份验证流程集成到您的应用程序中。
在这样做时,您是否曾经只在库的初始化代码中设置
issuer: "https://accounts.google.com"
就能自动解析 授权端点、令牌端点,甚至是公钥的位置(JWKS)?
- “仅提供 Issuer URL 为什么就能揭示所有端点?”
- “它如何在不产生停机的情况下跟随公钥(JWKS)轮换?”
- “首先,它是如何从类似电子邮件的 ID(如
alice@example.com)识别出要进行身份验证的提供者的?”
这些问题的答案是 OpenID Connect Discovery 1.0。
在过去的 OAuth 2.0 时代,开发者通常阅读文档并 硬编码 授权服务器的每个端点 URL(例如 /authorize、/token)到客户端。
这种做法在提供者更改 URL 或轮换公钥时需要客户端进行更改——显然不可扩展。
OIDC Discovery 1.0 是一种标准化的 机制,供客户端动态发现并检索 OpenID Provider(OP)的配置信息(元数据)。
在本文中,我们将基于规范深入探讨 OIDC Discovery 的两个阶段:
| 阶段 | 目标 |
|---|---|
| Issuer Discovery(阶段 1) | 根据用户提供的标识符(例如电子邮件地址)发现 谁 是应当对用户进行身份验证的 OpenID Provider(Issuer)。如果已经知道 Issuer(例如点击 “使用 Google 登录”),此步骤是可选的,可直接跳过。 |
| Provider Configuration(阶段 2) | 向已识别的 Issuer 查询其配置元数据——“你的授权端点在哪里?” “你的公钥(JWKS)在哪里?” |
何时需要 Issuer Discovery?
如果您的应用专门用于 Google 登录,Issuer 显然是 https://accounts.google.com。
但如果是企业 SaaS,需要 根据用户的电子邮件域(@company.com)动态切换目标 IdP,则 RFC 7033 WebFinger 就派上用场了。
标准化用户提供的标识符
用户输入的值可以是:
- 一个类似
alice@example.com的电子邮件地址 - 一个类似
https://example.com/alice的URL
OIDC Discovery 定义了严格的标准化步骤,以唯一确定:
- Host – 要联系的服务器
- Resource – 通过 WebFinger 查询的标识符
标准化规则
| 条件 | 标准化形式 |
|---|---|
无方案 且包含 @(例如 joe@example.com) | 解释为 acct: 方案 → acct:joe@example.com |
无方案 且不包含 @(例如 example.com 或 example.com:8080) | 解释为 https:// → https://example.com |
显式方案 (https://, acct:, …) | 保持原样,不作进一步更改 |
存在片段 (#…) | 完全移除片段 |
第 1 阶段 – 通过 WebFinger 发现 Issuer
假设用户输入了 joe@example.com,其规范化后为 acct:joe@example.com。
- 识别主机 – 提取 authority 部分(
example.com)。 - 指定资源 – 使用完整的规范化 URI(
acct:joe@example.com)作为resource查询参数。 - 调用 WebFinger 端点 – 对提取的主机上的
/.well-known/webfinger执行 HTTP GET 请求。
GET /.well-known/webfinger?resource=acct%3Ajoe%40example.com&rel=http%3A%2F%2Fopenid.net%2Fspecs%2Fconnect%2F1.0%2Fissuer HTTP/1.1
Host: example.com
resource– 要查询的 URL 编码标识符。rel– 固定值http://openid.net/specs/connect/1.0/issuer,表示“我正在请求 OIDC Issuer 信息”。
示例响应(JRD – JSON 资源描述符)
HTTP/1.1 200 OK
Content-Type: application/jrd+json
{
"subject": "acct:joe@example.com",
"links": [
{
"rel": "http://openid.net/specs/connect/1.0/issuer",
"href": "https://server.example.com"
}
]
}
href 值(https://server.example.com)是客户端将在下一阶段使用的 Issuer URL。
第 2 阶段 – 提供者配置
在已知 Issuer 后,客户端会检索 OP 的元数据。
OIDC 发现规范要求 OP 在一个路径下公开 JSON 元数据,该路径通过在 Issuer URL 后追加 /.well-known/openid-configuration 形成。
GET /.well-known/openid-configuration HTTP/1.1
Host: server.example.com
⚠️ 常见陷阱 – Issuer 包含路径
如果 Issuer 包含路径(例如 https://example.com/tenant-1):
- 移除任何尾随斜杠。
- 直接在路径后追加
/.well-known/openid-configuration。
得到的 URL:
https://example.com/tenant-1/.well-known/openid-configuration
不要错误地将配置文件放在域根目录下(https://example.com/.well-known/openid-configuration),因为许多实现都会这么做。
示例 OP 元数据响应
HTTP/1.1 200 OK
Content-Type: application/json
{
"issuer": "https://server.example.com",
"authorization_endpoint": "https://server.example.com/oauth2/v1/authorize",
"token_endpoint": "https://server.example.com/oauth2/v1/token",
"userinfo_endpoint": "https://server.example.com/oauth2/v1/userinfo",
"jwks_uri": "https://server.example.com/oauth2/v1/keys",
"registration_endpoint": "https://server.example.com/oauth2/v1/register",
"scopes_supported": ["openid", "profile", "email"],
"response_types_supported": ["code", "id_token", "token id_token"],
"grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
"subject_types_supported": ["public", "pairwise"],
"id_token_signing_alg_values_supported": ["RS256"],
"claims_supported": ["sub", "iss", "aud", "exp", "iat", "email", "email_verified", "name"]
}
JSON 对象(OP 元数据)列出了:
- 端点(
authorization_endpoint、token_endpoint、userinfo_endpoint、jwks_uri,…) - 支持的功能(
scopes_supported、grant_types_supported、claims_supported等)
有了这些元数据,客户端库可以自动:
- 将用户引导至正确的授权端点
- 在适当的令牌端点交换代码
- 从 JWKS URI 获取公钥(透明地处理密钥轮换)
回顾
- Issuer Discovery (可选) – 使用 WebFinger 将用户提供的标识符(电子邮件、URL 等)转换为 Issuer URL。
- Provider Configuration – 获取 Issuer 的
/.well-known/openid-configuration文档,以获取所有必需的端点和功能。
这个两阶段的发现过程消除了硬编码,支持多租户场景,并确保无缝的密钥轮换——全部在不中断服务的情况下完成。
OpenID Connect 提供者配置(发现)
{
"authorization_endpoint": "https://server.example.com/connect/authorize",
"token_endpoint": "https://server.example.com/connect/token",
"userinfo_endpoint": "https://server.example.com/connect/userinfo",
"jwks_uri": "https://server.example.com/jwks.json",
"response_types_supported": [
"code",
"id_token",
"id_token token"
],
"subject_types_supported": [
"public",
"pairwise"
],
"id_token_signing_alg_values_supported": [
"RS256",
"ES256"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"private_key_jwt"
],
"scopes_supported": [
"openid",
"profile",
"email"
],
"claims_supported": [
"sub",
"iss",
"name",
"email"
],
"registration_endpoint": "https://server.example.com/connect/register"
}
参数概览
| 参数名称 | 必需 / 可选 | 描述 |
|---|---|---|
| issuer | REQUIRED | OP的发行者标识符。用于TLS检查以及在ID Token中验证iss声明。 |
| authorization_endpoint | REQUIRED | 用户被重定向进行身份验证的端点。 |
| token_endpoint | REQUIRED* | 用于将授权码兑换为令牌的端点。除非OP仅支持隐式流。 |
| jwks_uri | REQUIRED | 公开密钥(JWK集合)所在的URL,用于验证ID Token的签名。 |
| response_types_supported | REQUIRED | OP支持的OIDC认证流(例如code、id_token、id_token token)。 |
| subject_types_supported | REQUIRED | sub标识符的类型——public(在所有RP之间相同)或pairwise(对每个RP唯一)。 |
| id_token_signing_alg_values_supported | REQUIRED | OP可用于ID Token的签名算法。RS256 必须包含。 |
| token_endpoint_auth_methods_supported | OPTIONAL | 在令牌端点接受的客户端认证方法(例如client_secret_basic、private_key_jwt)。 |
| scopes_supported | RECOMMENDED | OP支持的作用域列表。openid作用域应当存在。 |
| claims_supported | RECOMMENDED | OP可以返回的声明列表(例如name、email、sub、iss)。 |
| registration_endpoint | RECOMMENDED | 动态客户端注册的端点。 |
深入探讨:jwks_uri
- 为何重要 –
jwks_uri是 OP 公钥的唯一可信来源。通过获取该 URL,依赖方(RP)可以验证收到的每个 ID Token 的签名。 - 密钥轮换 – RP 应检查传入 ID Token 的
kid(密钥标识)头部。如果本地缓存中缺少对应的密钥,RP 必须重新从jwks_uri拉取 JWKS。这使得在不中断服务的情况下实现无缝密钥轮换。 - 安全考虑 –
-
冒充攻击:如果攻击者能够让 RP 下载恶意 JWKS,则伪造的 ID Token 将变得可行。
-
发现规范强制执行严格检查:
“返回的
issuer值 必须 与用作/.well-known/openid-configuration前缀的 Issuer URL 完全相同。”(OIDC Discovery §4.3)“实现 必须 支持 TLS。”(OIDC Discovery §7.1)
-
所有通信(WebFinger 和 Provider Configuration)必须通过 HTTPS 进行,RP 必须验证服务器的 TLS 证书(RFC 6125)。明文传输会让中间人篡改
jwks_uri为攻击者控制的服务器。
-
OIDC Discovery 1.0 的关键要点
- 不使用硬编码 URL – 使用
/.well-known/openid-configuration让 RP 在运行时发现 OP 的端点和功能。 - WebFinger 集成 – 像用户邮箱这样的标识符可以在发现阶段 1 中解析到正确的 Issuer。
- 自动化密钥轮换 – 动态获取 JWKS 实现了稳健、无缝的安全运维。
下次使用 OIDC 库时,想象一下对
/.well-known/openid-configuration的隐藏请求,这正是上述所有功能的驱动核心。
参考文献
- OpenID Connect Discovery 1.0 – 用于提供者元数据的核心规范。
- RFC 7033 – WebFinger – 用于发现资源信息的机制(例如电子邮件地址)。
- RFC 5785 – Well‑Known URIs – 定义用于发现端点的
/.well-known/命名空间。