Skip to Content
MUZINET-NOTE 4.0 is released 🎉

OpenID Connect Core 1.0 规范

目录

  1. 引言
    1.1. 需求符号和约定
    1.2. 术语
    1.3. 概述

  2. ID Token(身份令牌)

  3. 认证
    3.1. 通过授权码流程进行认证
    3.1.1. 授权码流程步骤
    3.1.2. 授权端点
    3.1.2.1. 认证请求
    3.1.2.2. 认证请求验证
    3.1.2.3. 授权服务器对终端用户进行身份验证
    3.1.2.4. 授权服务器获取终端用户的同意/授权
    3.1.2.5. 成功的认证响应
    3.1.2.6. 认证错误响应
    3.1.2.7. 认证响应验证
    3.1.3. 令牌端点
    3.1.3.1. 令牌请求
    3.1.3.2. 令牌请求验证
    3.1.3.3. 成功的令牌响应
    3.1.3.4. 令牌错误响应
    3.1.3.5. 令牌响应验证
    3.1.3.6. ID Token(身份令牌)
    3.1.3.7. ID Token 验证
    3.1.3.8. 访问令牌验证
    3.2. 通过隐式流程进行认证
    3.2.1. 隐式流程步骤
    3.2.2. 授权端点
    3.2.2.1. 认证请求
    3.2.2.2. 认证请求验证
    3.2.2.3. 授权服务器对终端用户进行身份验证
    3.2.2.4. 授权服务器获取终端用户的同意/授权
    3.2.2.5. 成功的认证响应
    3.2.2.6. 认证错误响应
    3.2.2.7. 重定向 URI 片段处理
    3.2.2.8. 认证响应验证
    3.2.2.9. 访问令牌验证
    3.2.2.10. ID Token(身份令牌)
    3.2.2.11. ID Token 验证
    3.3. 通过混合流程进行认证
    3.3.1. 混合流程步骤
    3.3.2. 授权端点
    3.3.2.1. 认证请求
    3.3.2.2. 认证请求验证
    3.3.2.3. 授权服务器对终端用户进行身份验证
    3.3.2.4. 授权服务器获取终端用户的同意/授权
    3.3.2.5. 成功的认证响应
    3.3.2.6. 认证错误响应
    3.3.2.7. 重定向 URI 片段处理
    3.3.2.8. 认证响应验证
    3.3.2.9. 访问令牌验证
    3.3.2.10. 授权码验证
    3.3.2.11. ID Token(身份令牌)
    3.3.2.12. ID Token 验证
    3.3.3. 令牌端点
    3.3.3.1. 令牌请求
    3.3.3.2. 令牌请求验证
    3.3.3.3. 成功的令牌响应
    3.3.3.4. 令牌错误响应
    3.3.3.5. 令牌响应验证
    3.3.3.6. ID Token(身份令牌)
    3.3.3.7. ID Token 验证
    3.3.3.8. 访问令牌
    3.3.3.9. 访问令牌验证

  4. 从第三方发起登录

  5. 声明
    5.1. 标准声明
    5.1.1. 地址声明
    5.1.2. 附加声明
    5.2. 声明语言和脚本
    5.3. UserInfo 端点
    5.3.1. UserInfo 请求
    5.3.2. 成功的 UserInfo 响应
    5.3.3. UserInfo 错误响应
    5.3.4. UserInfo 响应验证
    5.4. 使用 Scope 值请求声明
    5.5. 使用“claims”请求参数请求声明
    5.5.1. 独立声明请求
    5.5.1.1. 请求“acr”声明
    5.5.2. 独立声明的语言和脚本
    5.6. 声明类型
    5.6.1. 普通声明
    5.6.2. 聚合和分布式声明
    5.6.2.1. 聚合声明示例
    5.6.2.2. 分布式声明示例
    5.7. 声明的稳定性和唯一性

  6. 作为 JWT 的请求参数传递
    6.1. 通过值传递请求对象
    6.1.1. 使用 “request” 请求参数的请求
    6.2. 通过引用传递请求对象
    6.2.1. 引用请求对象的 URI
    6.2.2. 使用 “request_uri” 请求参数的请求
    6.2.3. 授权服务器获取请求对象
    6.2.4. “request_uri” 的依据
    6.3. 验证基于 JWT 的请求
    6.3.1. 加密的请求对象
    6.3.2. 签名的请求对象
    6.3.3. 请求参数的组装和验证

  7. 自签发的 OpenID 提供者
    7.1. 自签发的 OpenID 提供者发现
    7.2. 自签发的 OpenID 提供者注册
    7.2.1. 通过 “registration” 请求参数提供信息
    7.3. 自签发的 OpenID 提供者请求
    7.4. 自签发的 OpenID 提供者响应
    7.5. 自签发的 ID Token 验证

  8. 主题标识符类型
    8.1. 配对标识符算法

  9. 客户端身份验证

  10. 签名和加密
    10.1. 签名
    10.1.1. 非对称签名密钥的轮换
    10.2. 加密
    10.2.1. 非对称加密密钥的轮换

  11. 离线访问

  12. 使用刷新令牌
    12.1. 刷新请求
    12.2. 成功的刷新响应
    12.3. 刷新错误响应

  13. 序列化
    13.1. 查询字符串序列化
    13.2. 表单序列化
    13.3. JSON 序列化

  14. 字符串操作

  15. 实现考虑
    15.1. 所有 OpenID 提供者必须实现的功能
    15.2. 动态 OpenID 提供者必须实现的功能
    15.3. 发现和注册
    15.4. 依赖方必须实现的功能
    15.5. 实现注意事项
    15.5.1. 授权码实现注意事项
    15.5.2. Nonce 实现注意事项
    15.5.3. 重定向 URI 片段处理实现注意事项
    15.6. 兼容性注意事项
    15.7. 相关规范和实施者指南

  16. 安全注意事项
    16.1. 请求泄露
    16.2. 服务器伪装
    16.3. 令牌制造/修改
    16.4. 访问令牌泄露
    16.5. 服务器响应泄露
    16.6. 服务器响应抵赖
    16.7. 请求抵赖
    16.8. 访问令牌重定向
    16.9. 令牌重用
    16.10. 窃听或泄露授权码(辅助认证捕获)
    16.11. 令牌替换
    16.12. 时间攻击
    16.13. 其他与加密相关的攻击
    16.14. 签名和加密顺序

16.15. 发布者标识符(Issuer Identifier)
16.16. 隐式流的威胁(Implicit Flow Threats)
16.17. TLS 要求(TLS Requirements)
16.18. 访问令牌和刷新令牌的生命周期(Lifetimes of Access Tokens and Refresh Tokens)
16.19. 对称密钥的熵(Symmetric Key Entropy)
16.20. 请求需要签名(Need for Signed Requests)
16.21. 请求需要加密(Need for Encrypted Requests)
16.22. HTTP 307 重定向(HTTP 307 Redirects)
16.23. iOS 上的自定义 URI 方案(Custom URI Schemes on iOS)

  1. 隐私考虑(Privacy Considerations)
    17.1. 个人身份信息(Personally Identifiable Information)
    17.2. 数据访问监控(Data Access Monitoring)
    17.3. 关联(Correlation)
    17.4. 离线访问(Offline Access)

  2. IANA 考虑事项(IANA Considerations)
    18.1. JSON Web Token 声明注册(JSON Web Token Claims Registration)
    18.1.1. 注册内容(Registry Contents)
    18.2. OAuth 参数注册(OAuth Parameters Registration)
    18.2.1. 注册内容(Registry Contents)
    18.3. OAuth 扩展错误注册(OAuth Extensions Error Registration)
    18.3.1. 注册内容(Registry Contents)
    18.4. URI 方案注册(URI Scheme Registration)
    18.4.1. 注册内容(Registry Contents)

  3. 参考文献(References)
    19.1. 规范性参考文献(Normative References)
    19.2. 参考文献(Informative References)

附录 A. 授权示例(Authorization Examples)
A.1. 使用 response_type=code 的示例(Example using response_type=code)
A.2. 使用 response_type=id_token 的示例(Example using response_type=id_token)
A.3. 使用 response_type=id_token token 的示例(Example using response_type=id_token token)
A.4. 使用 response_type=code id_token 的示例(Example using response_type=code id_token)
A.5. 使用 response_type=code token 的示例(Example using response_type=code token)
A.6. 使用 response_type=code id_token token 的示例(Example using response_type=code id_token token)
A.7. 示例中使用的 RSA 密钥(RSA Key Used in Examples)

附录 B. 致谢(Acknowledgements)
附录 C. 通知(Notices)
§ 作者地址(Authors’ Addresses)

这些内容涉及 OpenID Connect 规范的技术细节、隐私保护以及一些注册机制,适用于在身份认证和授权领域使用 OpenID Connect 协议时的设计与实现。

1.OpenID Connect 1.0 简介

OpenID Connect 1.0 是一个简单的身份层,建立在 OAuth 2.0 协议 (RFC6749) 之上。它允许客户端在授权服务器执行身份验证后,验证最终用户的身份,并以可互操作的 REST 方式获取有关最终用户的基本概要信息。

OpenID Connect Core 1.0 规范定义了核心功能:

基于 OAuth 2.0 的身份验证 使用声明 (Claims) 传递有关最终用户的信息 该规范还描述了使用 OpenID Connect 的安全和隐私注意事项。

背景

OAuth 2.0 授权框架 (RFC6749) 和 OAuth 2.0 授权令牌使用 (RFC6750) 规范提供了一个通用框架,供第三方应用程序获取和使用对 HTTP 资源的有限访问权限。它们定义了获取和使用访问令牌以访问资源的机制,但没有定义提供身份信息的标准方法。值得注意的是,如果不扩展 OAuth 2.0,则它无法提供有关最终用户身份验证的信息。阅读本规范之前,用户应熟悉这些规范。

OpenID Connect 的工作原理

OpenID Connect 通过扩展 OAuth 2.0 授权过程来实现身份验证。客户端通过在授权请求中包含 openid 作用域值来请求使用此扩展。有关执行的身份验证的信息会以称为 ID 令牌的 JSON Web 令牌 (JWT) [JWT] (参见第 2 节) 返回。实现 OpenID Connect 的 OAuth 2.0 授权服务器也称为 OpenID 提供商 (OP)。使用 OpenID Connect 的 OAuth 2.0 客户端也称为依赖方 (RP)。

本规范假设依赖方已经获取了有关 OpenID 提供商的配置信息,包括其授权端点和令牌端点的位置。此信息通常通过发现 (Discovery) 获取,如 OpenID Connect Discovery 1.0 [OpenID.Discovery] 所述,也可以通过其他机制获取。

同样,本规范假设依赖方已经获取了足够的身份信息以及使用 OpenID 提供商所需的信息。这通常通过动态注册 (Dynamic Registration) 完成,如 OpenID Connect Dynamic Client Registration 1.0 [OpenID.Registration] 所述,也可以通过其他机制获取。

本规范的前序版本为:

OpenID Connect Core 1.0 包含勘误集 1 [OpenID.Core.Errata1] OpenID Connect Core 1.0 (最终版) [OpenID.Core.Final]

1.1. 需求表示法和约定

本文件中的以下关键词需按照 RFC 2119 [RFC2119] 的描述进行解释: “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“NOT RECOMMENDED”、“MAY” 和 “OPTIONAL”。

在本规范的 .txt 版本中,用引号括起的值表示这些值应按字面意思使用。在协议消息中使用这些值时,引号 MUST NOT(不得) 作为值的一部分被包含。在本规范的 HTML 版本中,按字面意思使用的值用固定宽度字体表示。

本规范中所有对 JSON Web Signature (JWS) [JWS] 和 JSON Web Encryption (JWE) [JWE] 数据结构的使用,都采用 JWS 紧凑序列化 或 JWE 紧凑序列化。 JWS JSON 序列化 和 JWE JSON 序列化 不被使用。

1.2. 术语

本规范使用以下术语及其定义:

来源术语

  • OAuth 2.0 [RFC6749] 定义的术语:
    “Access Token”、“Authorization Code”、“Authorization Endpoint”、“Authorization Grant”、“Authorization Server”、“Client”、“Client Authentication”、“Client Identifier”、“Client Secret”、“Grant Type”、“Protected Resource”、“Redirection URI”、“Refresh Token”、“Resource Server”、“Response Type”、“Token Endpoint”。

  • JSON Web Token (JWT) [JWT] 定义的术语:
    “Claim Name”、“Claim Value”、“JSON Web Token (JWT)”、“JWT Claims Set”、“Nested JWT”。

  • JSON Web Signature (JWS) [JWS] 定义的术语:
    “Base64url Encoding”、“Header Parameter”、“JOSE Header”。

  • RFC 7230 [RFC7230] 定义的术语:
    “User Agent”。

  • OAuth 2.0 多响应类型编码实践 [OAuth.Responses] 定义的术语:
    “Response Mode”。

定义的新术语

  1. Authentication
    用于在实体与其所呈现的身份之间建立足够信任的过程。

  2. Authentication Request
    使用 OpenID Connect 扩展参数和范围的 OAuth 2.0 授权请求,要求授权服务器(OpenID Connect Provider,简称 OP)对终端用户(End-User)进行身份验证并返回给客户端(OpenID Connect Relying Party)。

  3. Authentication Context
    依赖方在对身份验证响应进行授权决策前可以要求的信息。此上下文可包括但不限于实际使用的身份验证方法或认证保证级别(例如 ISO/IEC 29115 [ISO29115])。

  4. Authentication Context Class
    在特定上下文中被认为等价的一组身份验证方法或流程。

  5. Authentication Context Class Reference
    身份验证上下文类的标识符。

  6. Authorization Code Flow
    一种 OAuth 2.0 流程,其中授权码从授权端点返回,所有令牌从令牌端点返回。

  7. Authorization Request
    [RFC6749] 中定义的 OAuth 2.0 授权请求。

  8. Claim
    关于实体的一段声明信息。

  9. Claim Type
    表示声明值的语法。本规范定义了普通、聚合和分布式声明类型。

  10. Claims Provider
    可返回实体声明信息的服务器。

(下文按需补充,或继续翻译余下术语)

重要说明 本章节中的术语定义是本规范的规范性部分,对实现强加要求。在本规范文本中,所有以大写开头的词语(例如 “Issuer Identifier”)都引用这些定义。当读者遇到这些术语时,必须遵循其在本章节中的定义。

背景参考
有关部分术语的背景,可参阅:

  • Internet Security Glossary, Version 2 [RFC4949]
  • ISO/IEC 29115 实体身份验证保证 [ISO29115]
  • ITU-T X.1252 [X.1252]

1.3. 概述

OpenID Connect 协议的抽象过程如下:

  1. Relying Party (RP)(客户端)向 OpenID Provider (OP) 发送请求。
  2. OP 对终端用户(End-User)进行身份验证(Authentication)并获得授权(Authorization)。
  3. OP 返回一个 ID Token,通常还包括一个 Access Token。
  4. RP 使用 Access Token 向 UserInfo 端点发送请求。
  5. UserInfo 端点 返回关于终端用户的声明(Claims)。

这些步骤可通过以下示意图说明:

+--------+ +--------+ | | | | | |---------(1) AuthN Request-------->| | | | | | | | +--------+ | | | | | | | | | | | End- |<--(2) AuthN & AuthZ-->| | | | | User | | | | RP | | | | OP | | | +--------+ | | | | | | | |<--------(3) AuthN Response--------| | | | | | | |---------(4) UserInfo Request----->| | | | | | | |<--------(5) UserInfo Response-----| | | | | | +--------+ +--------+

流程描述

  1. 身份验证请求 (AuthN Request)
    RP 向 OP 发送请求,通常包含所需的范围(Scopes)、重定向 URI(Redirection URI)以及其他相关参数。

  2. 身份验证和授权 (AuthN & AuthZ)
    OP 验证终端用户的身份,并根据用户的同意授予相应的权限。

  3. 身份验证响应 (AuthN Response)
    OP 将 ID Token 和(通常)Access Token 返回给 RP,用以确认身份验证成功并提供后续访问权限。

  4. UserInfo 请求 (UserInfo Request)
    RP 使用 Access Token 向 UserInfo 端点发出请求,以获取与终端用户相关的详细声明信息。

  5. UserInfo 响应 (UserInfo Response)
    UserInfo 端点返回经过授权的关于终端用户的声明(例如姓名、电子邮件地址等)。

    2. ID Token

ID Token 是 OpenID Connect 对 OAuth 2.0 的核心扩展,用于实现终端用户的身份认证(Authentication)。ID Token 是一种安全令牌(Security Token),包含关于授权服务器对终端用户身份认证的声明(Claims),以及其他可能请求的声明。这种令牌采用 JSON Web Token (JWT) 格式表示。

必需和可选的 ID Token Claims

以下是 OpenID Connect 在 OAuth 2.0 流程中使用的 ID Token 声明:

  1. iss (Issuer)

    • 必需:响应发出者的标识符。
    • 值为区分大小写的 HTTPS URL,包含方案、主机,且可选地包含端口号和路径组件。
    • 不应包含查询或片段组件。
  2. sub (Subject)

    • 必需:用户的标识符。
    • 在相同的 iss 范围内唯一,且不会重新分配。
    • 不得超过 255 个 ASCII 字符。
  3. aud (Audience)

    • 必需:该 ID Token 的目标受众。
    • 必须包含 RP 的 OAuth 2.0 client_id,并可包含其他受众标识符。
    • 可以是字符串数组或单个字符串。
  4. exp (Expiration Time)

    • 必需:ID Token 过期时间。
    • 当前时间必须小于 exp 的值。值为自 UTC 时间 1970-01-01T00:00:00Z 起的秒数。
  5. iat (Issued At)

    • 必需:JWT 签发时间。
    • 值为自 UTC 时间 1970-01-01T00:00:00Z 起的秒数。
  6. auth_time (Authentication Time)

    • 可选:用户身份验证发生的时间。
    • 如果请求中指定了 max_age 或将 auth_time 声明设为必要,则此声明为必需。
  7. nonce

    • 可选:用于将客户端会话与 ID Token 关联的字符串值,同时可用于防止重放攻击。
    • 客户端和服务器需验证 nonce 的一致性。
  8. acr (Authentication Context Class Reference)

    • 可选:指定身份验证上下文的字符串标识符。
    • 值可能为绝对 URI 或注册名称,具体含义需要双方协商一致。
  9. amr (Authentication Methods References)

    • 可选:身份验证方法的标识符数组。例如,可能值为密码和一次性密码 (OTP)。
  10. azp (Authorized Party)

    • 可选:被授权的一方(通常是客户端的 client_id)。

ID Token 的安全性

  • 签名与加密

    • ID Token 必须使用 JWS 签名,且可选择加密(JWE)。
    • 若加密,则必须先签名后加密,生成嵌套 JWT (Nested JWT)。
  • 算法约束

    • ID Token 不得使用 none 作为签名算法,除非明确在注册时请求,并且授权端点不返回 ID Token。

示例 ID Token Claims

以下是一个非规范性示例:

{ "iss": "https://server.example.com", "sub": "24400320", "aud": "s6BhdRkqt3", "nonce": "n-0S6_WzA2Mj", "exp": 1311281970, "iat": 1311280970, "auth_time": 1311280969, "acr": "urn:mace:incommon:iap:silver" }

扩展支持
ID Token 可以包含额外的声明,未被理解的声明应被忽略。有关其他声明的详细信息,可参见 OpenID Connect 规范的相关章节。

3. Authentication

OpenID Connect 的主要功能是通过身份认证(Authentication)帮助终端用户登录,或者判断终端用户是否已经登录。认证的结果以一种安全的方式从服务器返回给客户端(称为依赖方,Relying Party, RP),从而使客户端能够信赖该认证结果。

认证结果通过 ID Token 返回,其中包含关于认证的信息(如发行者、用户标识符、认证时间等)。

认证流程类型

OpenID Connect 支持以下三种认证流程:

  1. 授权码流程(Authorization Code Flow)

    • 使用 response_type=code
    • 最安全的方式,因为所有令牌均从服务器到服务器传输,避免暴露给用户代理(如浏览器)。
    • 支持刷新令牌(Refresh Token)。
  2. 隐式流程(Implicit Flow)

    • 使用 response_type=id_tokenresponse_type=id_token token
    • 适用于不安全环境(如纯前端应用),因为令牌直接从授权端点返回,避免复杂的服务器端通信。
    • 无法获得刷新令牌,且所有令牌暴露给用户代理。
  3. 混合流程(Hybrid Flow)

    • 使用 response_type=code id_tokenresponse_type=code tokenresponse_type=code id_token token
    • 同时支持授权码和隐式流程的部分功能,适合特定的复杂需求。

认证流程比较

属性授权码流程隐式流程混合流程
令牌是否全部从授权端点返回
令牌是否全部从令牌端点返回
令牌是否对用户代理隐藏
客户端是否能够认证
是否支持刷新令牌
是否仅需要单次通信
是否主要通过服务器端通信视情况而定

响应类型与流程的对应关系

response_type流程类型
code授权码流程
id_token隐式流程
id_token token隐式流程
code id_token混合流程
code token混合流程
code id_token token混合流程

注意事项

  • response_type=code 定义于 OAuth 2.0 规范 (RFC 6749)。
  • 其他响应类型(如 id_tokenid_token token)定义于 OAuth 2.0 多重响应类型编码规范
  • 虽然 OAuth 2.0 定义了 token 作为隐式流程的响应类型,但 OpenID Connect 不使用它,因为其不返回 ID Token。

这种灵活性允许开发人员根据应用的安全需求、复杂性和用户体验要求选择最合适的认证流程。

3.1. 使用授权码流程进行身份验证

Authorization Code Flow 是 OpenID Connect 最安全的认证流程之一,适用于能够安全存储客户端密钥(Client Secret)的客户端。

流程描述

  1. 请求授权码
    客户端向授权端点发送请求,用户代理(如浏览器)将用户认证并授权后,授权端点返回 授权码(Authorization Code) 给客户端。

  2. 交换令牌
    客户端将授权码发送至令牌端点,授权服务器验证后返回以下令牌:

    • ID Token:标识终端用户并提供认证信息。
    • 访问令牌(Access Token):用于访问受保护资源的权限凭证。

优势

  • 令牌未暴露给用户代理
    授权码通过用户代理传输,但实际的令牌交换通过服务器到服务器的通信完成,减少令牌被截获的风险。

  • 客户端认证
    授权服务器可以验证客户端密钥,以确保授权码只被可信的客户端使用。

  • 支持刷新令牌
    客户端可以获得刷新令牌,用于获取新的访问令牌,从而避免频繁重新认证。

适用场景

  • 服务器端应用
    适合能够安全存储客户端密钥的服务器端应用。

  • 高安全需求
    特别适合需要最大化减少敏感数据暴露风险的场景,例如涉及用户隐私或金融交易的系统。

总结
授权码流程通过在多个步骤中隔离令牌的传输和存储,最大程度地提高了认证过程的安全性,是大多数情况下推荐使用的认证流程。

3.1.1. Authorization Code Flow Steps

授权码流程 由以下步骤组成:


1. 客户端准备认证请求
客户端生成一个认证请求,包含以下必要的请求参数:

  • client_id:客户端标识符。
  • redirect_uri:用户成功认证后跳转的回调地址。
  • response_type:设置为 code,表示请求授权码。
  • scope:请求的权限范围(通常包括 openid 和其他相关权限)。
  • state:一个防止 CSRF 攻击的值,客户端应验证该值的一致性。
  • 可选参数:nonce 用于防止重放攻击,特别是对 ID Token 的校验有帮助。

2. 客户端向授权服务器发送请求
客户端将准备好的请求发送至授权服务器的授权端点,通常以 HTTPS GET 请求的形式发送。


3. 授权服务器认证终端用户
授权服务器引导终端用户登录,并进行身份验证。例如,用户可能需要输入用户名和密码。


4. 授权服务器获取用户同意/授权
在用户认证成功后,授权服务器向用户请求授权,以允许客户端访问其资源。用户可以同意或拒绝该请求。


5. 授权服务器返回授权码
若用户同意授权,授权服务器通过重定向,将用户带回客户端指定的 redirect_uri,并在查询参数中附加授权码:

https://client.example.com/cb?code=AUTHORIZATION_CODE&state=STATE

客户端需要验证 state 值,以确保请求未被篡改。


6. 客户端使用授权码请求令牌
客户端通过安全的后端通信,将授权码发送到令牌端点以交换令牌。请求中包含以下参数:

  • grant_type:设置为 authorization_code
  • code:从授权服务器获取的授权码。
  • redirect_uri:必须与最初发送的 redirect_uri 匹配。
  • client_idclient_secret:用于认证客户端。

7. 客户端接收 ID Token 和访问令牌
授权服务器验证请求后,返回一个响应,其中包括:

  • ID Token:包含用户身份信息(如 sub)。
  • Access Token:用于访问受保护资源。

示例响应:

{ "id_token": "eyJhbGciOiJIUzI1NiIs...", "access_token": "SlAV32hkKG", "expires_in": 3600, "token_type": "Bearer" }

8. 客户端验证 ID Token 并获取用户标识
客户端验证 ID Token 的签名、颁发者(iss)、受众(aud)、过期时间(exp)等信息。
验证通过后,客户端可从 ID Tokensub 字段中提取终端用户的唯一标识符。


总结
授权码流程通过多步骤分离认证和令牌的传输,确保敏感信息的安全性。它的高安全性使其成为大多数应用(特别是服务端应用)使用的首选认证方式。

3.1.2. Authorization Endpoint

授权端点 的主要功能是对终端用户进行认证和授权。客户端通过重定向用户代理(如浏览器)到授权端点,触发用户的认证与授权流程。


功能说明

  1. 用户认证
    授权端点负责验证终端用户的身份。例如,用户需要提供用户名和密码,或者通过多因素认证完成登录。

  2. 用户授权
    在认证成功后,授权端点会引导用户同意(或拒绝)授予客户端访问其资源的权限。例如,用户可能会被提示允许客户端访问其电子邮件或个人信息。


请求参数

客户端发起的请求需要包含以下参数:

  • client_id: 客户端的唯一标识符。
  • response_type: 指定期望的授权类型。例如,code 表示授权码流程。
  • redirect_uri: 用户认证和授权成功后,授权服务器重定向用户代理到该 URI。
  • scope: 客户端请求的权限范围。对于 OpenID Connect,必须至少包括 openid
  • state: 客户端生成的随机字符串,用于防止 CSRF 攻击。授权服务器会在重定向时将该值返回。
  • 可选参数:
    • nonce: 用于防止重放攻击,特别是在 ID Token 验证中使用。
    • prompt: 指定用户界面提示行为,例如强制用户重新登录。

示例请求
以下是一个典型的授权端点请求:

GET /authorize?client_id=s6BhdRkqt3 &response_type=code &scope=openid%20email &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb &state=af0ifjsldkj &nonce=n-0S6_WzA2Mj HTTP/1.1 Host: server.example.com

安全要求

  • 强制使用 TLS:
    所有与授权端点的通信必须使用传输层安全协议(TLS),以防止敏感数据被拦截。
    具体要求参考 Section 16.17 

  • 验证重定向 URI:
    授权服务器必须验证请求中的 redirect_uri 与客户端注册时的 URI 是否匹配,以防止重定向攻击。


总结
授权端点是 OpenID Connect 工作流程的核心组件之一,承担了用户认证和授权的任务。通过 TLS 和参数验证机制,授权端点保证了认证过程的安全性和可靠性。

3.1.2.1. Authentication Request

认证请求 是 OAuth 2.0 授权请求的一个特定类型,用于要求授权服务器对终端用户进行身份验证。


支持的请求方法
授权服务器必须支持以下 HTTP 方法在授权端点上进行通信:

  1. GET: 参数通过 URI 查询字符串序列化(参考 Section 13.1 )。
  2. POST: 参数通过表单序列化(参考 Section 13.2 )。

客户端可选择使用 GET 或 POST 方法发送认证请求。


主要请求参数

参数是否必需描述
scope必需必须包含 openid 值,以指明请求是 OpenID Connect 认证请求。可以附加其他范围,如 emailprofile
response_type必需决定使用的授权处理流程。在授权码流程中,该值必须为 code
client_id必需客户端的唯一标识符,在授权服务器上注册时生成。
redirect_uri必需用户认证后的回调 URI,必须与客户端注册时的值完全匹配。支持 https 和某些特定情况下的 http
state推荐随机字符串,用于防止 CSRF 攻击。授权服务器会将其原样返回。
nonce可选用于关联客户端会话与 ID Token,防止重放攻击。
response_mode可选指定返回参数的机制。如果未指定,使用默认模式。
prompt可选控制服务器是否提示用户重新认证、同意授权等操作(例如 loginconsentnone 等)。
max_age可选最大认证年龄(秒)。如果用户上次认证时间超过此值,则需重新认证。
ui_locales可选指定用户界面显示语言,使用 BCP47 语言标签格式。
id_token_hint可选提供之前由授权服务器颁发的 ID Token,用作用户当前或过去会话的提示。
login_hint可选提示用户可能使用的登录标识符,例如邮箱或手机号。
acr_values可选指定优先的认证上下文类引用(Authentication Context Class Reference)。

请求示例

使用 HTTP GET 的认证请求

GET /authorize? response_type=code &scope=openid%20profile%20email &client_id=s6BhdRkqt3 &state=af0ifjsldkj &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1 Host: server.example.com

使用 HTTP POST 的认证请求

POST /authorize HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded response_type=code &scope=openid%20profile%20email &client_id=s6BhdRkqt3 &state=af0ifjsldkj &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb

响应示例

授权服务器通过 HTTP 302 重定向用户代理到客户端指定的 redirect_uri,并附加授权码和状态参数:

HTTP/1.1 302 Found Location: https://client.example.org/cb? code=SplxlOBeZQQYbYS6WxSbIA &state=af0ifjsldkj

安全性要求

  1. TLS 加密:
    所有请求和响应必须通过 TLS 加密传输,保护敏感数据不被窃取或篡改。

  2. 重定向 URI 验证:
    授权服务器必须验证 redirect_uri 是否与客户端注册时的 URI 完全匹配,避免重定向攻击。

  3. 状态参数:
    强烈建议使用 state 参数来防止 CSRF 攻击。


总结
认证请求是 OpenID Connect 中至关重要的一部分。通过支持多种可选参数和严格的安全要求,它能够满足不同的认证场景,同时确保数据的机密性和完整性。

3.1.2.2. 授权请求校验流程解读

根据 OpenID Connect 的规范,授权服务器在处理认证请求时,需要进行以下校验步骤:


1. 校验 OAuth 2.0 参数 授权服务器必须按照 OAuth 2.0 规范验证请求中的参数是否符合要求,包括但不限于以下内容:

  • response_type
  • client_id
  • redirect_uri
  • 其他 OAuth 2.0 定义的参数。

2. 校验 scope 参数

  • 必须验证请求是否包含 scope 参数。
  • 该参数必须包含 openid 值。如果缺少 openid,这说明请求不是 OpenID Connect 规范的认证请求,可能只是普通的 OAuth 2.0 请求。

3. 校验必需参数

  • 授权服务器必须验证所有必需参数是否存在且符合 OpenID Connect 的要求。例如:
    • client_id
    • response_type
    • redirect_uri
  • 还需确保这些参数的用法符合规范。

4. 校验 sub Claim

  • 如果客户端请求返回 ID Token 中包含特定值的 sub(主体)Claim,则需要满足以下条件:

    • 授权服务器只能在确认该 sub 值所标识的用户有一个活动会话,或者由于此请求成功完成了用户认证时,才返回一个正向响应。
    • 不得为其他用户返回 ID Token 或 Access Token,即使这些用户在授权服务器上有活动会话。

    请求 sub Claim 的方法:

    • 使用 id_token_hint 参数。
    • 使用 claims 参数(需要实现支持该参数)。

5. 校验 id_token_hint 参数

  • 如果请求中包含 id_token_hint 参数,授权服务器必须确认它是由自身签发的。
  • 即使 id_tokenexp(过期时间)已经过期,授权服务器仍可以接受该 id_token,前提是:
    • RP(客户端)与授权服务器之间的会话是当前会话或者近期的会话。

6. 忽略未识别的参数

  • 如果请求中包含授权服务器未识别的参数,授权服务器应按照 OAuth 2.0 的要求忽略这些参数,而不是返回错误。

7. 错误处理

  • 如果授权服务器在验证过程中发现任何错误(如参数缺失、值不符合规范),必须按照 3.1.2.6  节的要求返回错误响应。
  • 错误响应内容包括:
    • error: 错误代码,例如 invalid_request
    • error_description(可选):对错误的简要描述。

这个校验过程确保了请求的安全性和规范性,同时为客户端和最终用户提供了清晰的错误处理流程。是否需要进一步解析特定步骤或提供代码实现示例?

3.1.2.3. 授权服务器身份认证过程

在 OpenID Connect 认证流程中,授权服务器在验证请求有效性后,进行以下的 End-User(最终用户)身份认证过程。


1. 身份认证尝试

  • 如果请求有效,授权服务器将尝试对 End-User 进行身份认证,或者根据请求中的参数判断 End-User 是否已经通过身份认证。
  • 身份认证的方法(如用户名和密码、会话 Cookie 等)并不在此规范的范围内。
  • 授权服务器可能会根据请求参数和使用的认证方法显示身份认证用户界面。

2. 必须进行身份认证的情况 授权服务器必须在以下情况下尝试进行身份认证:

  • End-User 未经过身份认证:如果 End-User 尚未通过身份认证,授权服务器必须进行身份验证。
  • 请求中包含 prompt=login 参数:如果认证请求中包含 prompt=login 参数,授权服务器必须强制重新认证 End-User,即使该用户已经通过身份认证。

3. 不进行身份认证的情况

  • 请求中包含 prompt=none 参数:如果请求中包含 prompt=none 参数,授权服务器必须在以下情况中返回错误:
    • End-User 尚未认证;
    • 无法通过静默认证的方式认证用户。

4. 防护措施

  • 在与 End-User 交互时,授权服务器必须采取适当的措施防范 跨站请求伪造(CSRF)点击劫持(Clickjacking) 攻击。这些防护措施必须符合 OAuth 2.0 [RFC6749] 中的 10.1210.13 节的要求。

总结

授权服务器的身份认证过程包括检查用户是否已经认证、是否需要重新认证,以及在特定情况下是否允许用户在无交互的情况下通过身份认证。通过引入 prompt 参数和相应的安全防护措施,授权服务器能够灵活地处理用户身份认证,同时确保过程中的安全性,防止常见的攻击风险。

3.1.2.4. 授权服务器获取 End-User 同意/授权

在 OpenID Connect 认证流程中,一旦 End-User 成功身份认证,授权服务器需要获取用户的授权决定,才能向依赖方(Relying Party)释放信息。授权服务器获取用户同意的过程如下:


1. 获取授权决定

  • 授权服务器必须在释放信息之前,获取 End-User 的授权决定。
  • 这种同意获取的方式可以通过多种方式实现,具体取决于请求中使用的参数。

2. 获取同意的方式

  • 交互式对话:在某些情况下,授权服务器可以通过与 End-User 进行交互式对话来获得授权,使用户明确知道他们正在同意什么内容。此方式通常用于用户需要明确授权的情形。

  • 通过条件或先前的行政同意:在某些情况下,授权服务器可以通过设置特定的条件来获取用户的同意,或者基于先前的行政同意来确认授权。例如,用户可能已经在之前的交互中同意过相关授权。


3. 信息释放机制

  • 相关的信息释放机制在 Section 2Section 5.3 中进行了详细描述。这些机制为授权服务器提供了多种释放用户信息的方式,确保用户同意后信息的正确传递。

总结

授权服务器在身份认证后,必须获得 End-User 的同意或授权,才能向依赖方发布信息。这个授权过程可以通过交互式对话或通过先前的授权设置条件来实现。确保用户同意的机制有助于提高整个认证流程的透明度,并确保信息释放符合用户的授权意图。

3.1.2.5. 成功的身份认证响应

身份认证响应是 OAuth 2.0 授权响应消息,由授权服务器的授权端点(Authorization Endpoint)返回,用于响应由依赖方(Relying Party, RP)发送的授权请求(Authorization Request)消息。


1. 使用授权码流(Authorization Code Flow)

当使用授权码流时,授权响应必须返回 OAuth 2.0 [RFC6749] 第 4.1.2 节定义的参数,并将这些参数作为查询参数附加到在授权请求中指定的 redirect_uri 中,格式为 application/x-www-form-urlencoded,除非指定了不同的响应模式。


2. 示例响应

以下是一个非规范性的成功响应示例,使用该流时的 HTTP 响应(为了显示方便,值中的换行仅为展示所用):

HTTP/1.1 302 Found Location: https://client.example.org/cb? code=SplxlOBeZQQYbYS6WxSbIA &state=af0ifjsldkj

3. 授权码的内容

有关授权码内容的实现说明,请参见 Section 15.5.1


总结

成功的身份认证响应包含授权码流中的参数,这些参数通过查询字符串的形式传递给客户端的重定向 URI。具体的实现细节可参考 OAuth 2.0 的相关规范以及授权码的内容说明。

3.1.2.6. 身份认证错误响应

身份认证错误响应是由授权服务器(OP)的授权端点返回的 OAuth 2.0 授权错误响应消息,用于回应依赖方(RP)发送的授权请求消息。


1. 错误情形

如果 End-User 拒绝请求或身份认证失败,授权服务器(OP)通过使用 OAuth 2.0 [RFC6749] 第 4.1.2.1 节中定义的错误响应参数来通知依赖方(客户端)。如果是与 RFC 6749 无关的 HTTP 错误,将使用适当的 HTTP 状态码返回给用户代理。

除非重定向 URI 无效,否则授权服务器将客户端重定向到授权请求中指定的重定向 URI,并附带适当的错误和状态参数。其他参数不应返回。如果重定向 URI 无效,授权服务器必须不会将用户代理重定向到该无效的重定向 URI。


2. 响应模式未支持时

如果不支持响应模式(Response Mode),授权服务器将返回 HTTP 响应码 400(错误请求),且不包括错误响应参数,因为理解响应模式对于知道如何返回这些参数是必要的。


3. 定义的额外错误代码

除了 OAuth 2.0 第 4.1.2.1 节中定义的错误代码外,本规范还定义了以下错误代码:

  • interaction_required
    授权服务器需要进行某种形式的 End-User 交互才能继续。此错误可以在身份认证请求中的 prompt 参数值为 none 时返回,但身份认证请求无法在没有用户界面交互的情况下完成。

  • login_required
    授权服务器要求进行 End-User 身份认证。此错误可以在身份认证请求中的 prompt 参数值为 none 时返回,但身份认证请求无法在没有用户界面身份认证的情况下完成。

  • account_selection_required
    End-User 必须在授权服务器上选择一个会话。End-User 可能已通过不同的账户在授权服务器上进行身份认证,但未选择一个会话。如果身份认证请求中的 prompt 参数值为 none,但请求无法在没有显示用户界面的情况下完成时,可以返回此错误。

  • consent_required
    授权服务器要求 End-User 授权。此错误可以在身份认证请求中的 prompt 参数值为 none 时返回,但身份认证请求无法在没有用户界面同意的情况下完成。

  • invalid_request_uri
    身份认证请求中的 request_uri 返回错误或包含无效数据。

  • invalid_request_object
    请求参数包含无效的请求对象。

  • request_not_supported
    授权服务器不支持第 6 节中定义的 request 参数。

  • request_uri_not_supported
    授权服务器不支持第 6 节中定义的 request_uri 参数。

  • registration_not_supported
    授权服务器不支持第 7.2.1 节中定义的 registration 参数。


4. 错误响应参数

  • error
    必需的。错误代码。

  • error_description
    可选的。可读的 ASCII 编码错误描述。

  • error_uri
    可选的。包含错误详细信息的 URI。

  • state
    OAuth 2.0 状态值。若授权请求中包含 state 参数,则必需提供并设置为从客户端接收到的值。


5. 授权码流中的错误响应

在使用授权码流时,错误响应参数将被添加到重定向 URI 的查询部分,除非指定了不同的响应模式。


6. 错误响应示例

以下是使用该流的非规范性错误响应示例(为了显示目的,值中的换行仅为展示所用):

HTTP/1.1 302 Found Location: https://client.example.org/cb? error=invalid_request &error_description=Unsupported%20response_type%20value &state=af0ifjsldkj

总结

身份认证错误响应包含在授权请求过程中可能遇到的错误和特定的错误代码。响应还包括与错误相关的描述信息和状态值,以及在授权码流中,错误响应参数将被添加到重定向 URI 的查询部分。

3.1.2.7. 身份认证响应验证

在使用授权码流(Authorization Code Flow)时,客户端必须按照 RFC 6749  的规定对响应进行验证,特别是以下部分:

  • 第 4.1.2 节:授权码授予(Authorization Code Grant)中的响应处理要求。
  • 第 10.12 节:授权服务器与客户端之间的安全性建议,特别是防止跨站请求伪造(CSRF)和其他攻击的安全措施。

客户端需要严格遵守这些规定,以确保身份认证流程的安全性和数据的完整性。

3.1.3. 令牌端点(Token Endpoint)

为了获取访问令牌(Access Token)、ID 令牌(ID Token)以及可选的刷新令牌(Refresh Token),在使用授权码流(Authorization Code Flow)时,Relying Party(RP,即客户端)会向令牌端点发送令牌请求(Token Request),以获取令牌响应(Token Response)。相关描述详见 OAuth 2.0 RFC6749  的第 3.2 节。

与令牌端点的通信必须使用 TLS(传输层安全协议)。关于使用 TLS 的更多信息,请参见第 16.17 节。

以下是对 Token 请求过程的中文翻译:

3.1.3.1. Token 请求

客户端通过向 Token 端点 提交其 授权许可(以 授权码 形式),并使用 grant_type 值为 authorization_code 发起 Token 请求,如 OAuth 2.0 的第 4.1.3 节所述。如果客户端是 机密客户端,则必须使用已为其 client_id 注册的身份验证方法对 Token 端点进行身份验证,如第 9 节所述。

客户端使用 HTTP POST 方法,并根据 OAuth 2.0 第 4.1.3 节描述的 表单序列化(Form Serialization)向 Token 端点发送参数。

下面是一个非规范性的 Token 请求示例(为了显示的目的,值内有换行):

POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb

参数说明:

  • grant_type:该值应始终设置为 authorization_code,表示采用授权码授权流程。
  • code:客户端从 授权端点 获得的 授权码
  • redirect_uri:客户端在最初的授权请求中使用的 重定向 URI。此值必须与最初使用的重定向 URI 相匹配。

机密客户端的身份验证:

  • 如果客户端是 机密客户端,则必须使用在其 client_id 上注册的身份验证方法进行身份验证。
3.1.3.2. Token 请求验证

授权服务器必须对 Token 请求进行如下验证:

  1. 验证客户端身份
    如果客户端已经获得客户端凭证(Client Credentials)或使用了其他客户端认证方法,则必须按照第 9 节的要求对客户端进行身份验证。

  2. 确保授权码是颁发给已认证客户端的
    授权服务器需要验证该授权码确实是颁发给当前已认证的客户端。

  3. 验证授权码的有效性
    授权服务器必须确保该授权码仍然有效。

  4. 如果可能,验证授权码未被重复使用
    授权服务器需要检查授权码是否已经被使用过(如果有机制支持检测此项)。

  5. 验证 redirect_uri 参数的值是否一致
    授权服务器需要确保 redirect_uri 参数的值与初始授权请求中包含的 redirect_uri 参数值完全一致。如果只有一个注册的 redirect_uri 值且请求中没有包含 redirect_uri 参数,授权服务器可以选择返回错误(因为客户端应该包含该参数),或者可以不返回错误(因为 OAuth 2.0 允许在这种情况下省略该参数)。

  6. 验证授权码是由 OpenID Connect 认证请求生成的
    授权服务器需要确保使用的授权码是响应 OpenID Connect 认证请求而颁发的(以便从 Token 端点返回 ID Token)。

    3.1.3.3. 成功的 Token 响应

在接收到并验证有效且授权的 Token 请求后,授权服务器返回一个成功的响应,其中包括 ID TokenAccess Token。成功响应中的参数定义请参考 OAuth 2.0 第 4.1.4 节 [RFC6749]。响应使用 application/json 媒体类型。

响应中的 token_type 参数

  • 响应中的 token_type 必须为 Bearer,如在 OAuth 2.0 Bearer Token 使用 [RFC6750] 中所指定,除非与客户端协商了其他 Token 类型。
  • 服务器应该支持 Bearer Token 类型;其他 Token 类型的使用超出了本规范的范围。
  • 请注意,token_type 的值不区分大小写。

除了 OAuth 2.0 中指定的响应参数外,以下参数必须包含在响应中:

  • id_token:与认证会话相关联的 ID Token 值。

安全性要求 所有包含令牌、密钥或其他敏感信息的 Token 响应必须包含以下 HTTP 响应头字段及其值:

Header NameHeader Value
Cache-Controlno-store

成功的 Token 响应示例 以下是一个非规范性的成功 Token 响应示例。示例中的 ID Token 签名可以通过附录 A.7 中的密钥进行验证:

HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "access_token": "SlAV32hkKG", "token_type": "Bearer", "refresh_token": "8xLOxBtZp8", "expires_in": 3600, "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5 NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4 XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg" }

客户端处理未识别的响应参数 如 OAuth 2.0 [RFC6749] 所规定,客户端应该忽略未识别的响应参数。

3.1.3.4. Token 错误响应

如果 Token 请求 无效或未经授权,授权服务器会构造一个错误响应。Token 错误响应 的参数定义请参考 OAuth 2.0 第 5.2 节 [RFC6749]。HTTP 响应正文使用 application/json 媒体类型,且 HTTP 响应代码为 400。

错误响应示例 以下是一个非规范性的 Token 错误响应 示例:

HTTP/1.1 400 Bad Request Content-Type: application/json Cache-Control: no-store { "error": "invalid_request" }

错误响应参数

  • error:指示请求错误的类型。例如,上面的示例中是 "invalid_request",表示请求无效。

对于不同的错误类型,error 参数可能包含其他具体错误码,OAuth 2.0 [RFC6749] 定义了多种可能的错误类型,如 invalid_clientinvalid_grant 等。

3.1.3.5. Token 响应验证

客户端必须按以下规则验证 Token 响应

  1. 遵循 RFC 6749 的验证规则
    特别是参考以下章节的规则:

    • 第 5.1 节:关于成功响应的处理。
    • 第 10.12 节:关于安全性相关的注意事项。
  2. 遵循 ID Token 的验证规则
    按照第 3.1.3.7 节 的说明验证 ID Token

  3. 遵循 Access Token 的验证规则
    按照第 3.1.3.8 节 的说明验证 Access Token

通过上述验证步骤,客户端可以确保接收到的 Token 响应是有效且可信的,同时遵循 OpenID Connect 和 OAuth 2.0 的相关规范。

3.1.3.6. ID Token

ID Token 的内容如第 2 节所述。当使用 Authorization Code 流程 时,对于以下 ID Token 声明 (Claims),需满足附加要求:


at_hash

  • 可选 (OPTIONAL)
    at_hash 是 Access Token 的哈希值。

  • 值的计算方法

    • 对 Access Token 的 ASCII 表示形式的字节进行哈希计算,使用的哈希算法与 ID Token 的 JOSE Header 中的 alg 参数指定的算法一致。
    • 例如:如果 alg 参数是 RS256,则使用 SHA-256 对 Access Token 进行哈希。
    • 提取哈希结果的前 128 位(左侧一半)。
    • 使用 base64url 对上述值进行编码。
  • 值的特性
    at_hash 的值是区分大小写的字符串。


示例
如果 algRS256 且 Access Token 是 abc123

  1. 使用 SHA-256abc123 进行哈希,结果可能是:
    b1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
  2. 提取前 128 位:
    b1234567890abcdef1234567890abcdef
  3. 将提取的值用 base64url 编码:
    base64url(b1234567890abcdef1234567890abcdef) = "somestring"

最终,at_hash 值为 “somestring”


at_hash 提供了一种验证机制,以确保 Access Token 的完整性和关联性,有助于增强安全性。

3.1.3.7. ID Token 验证

客户端必须按照以下规则验证 Token 响应 中的 ID Token


  1. 解密 ID Token
  • 如果 ID Token 已加密,客户端必须使用其在注册期间指定的密钥和算法解密该 Token。
  • 如果在注册时与 OpenID Provider (OP) 协商了加密,但 ID Token 未加密,客户端 (RP) 应当 拒绝该 Token。

  1. 验证 iss (Issuer) 声明
  • ID Tokeniss 声明(发行方标识符)必须与 OpenID Provider 的发行者标识符完全匹配(通常在发现过程 [Discovery] 中获得)。

  1. 验证 aud (Audience) 声明
  • aud 声明必须包含客户端的 client_id 值,表示该 Token 的受众是客户端。
  • aud 声明可以是一个数组,包含多个受众:
    • 如果 aud 不包含客户端作为受众,或者包含客户端不信任的其他受众,客户端必须拒绝 ID Token

  1. 验证 azp (Authorized Party) 声明
  • 如果使用了扩展功能(超出本规范范围),导致 azp 声明出现,客户端应按扩展的规定验证 azp 的值。
  • 当存在 azp 声明时,客户端应验证其 client_id 是否与声明值一致。

  1. 验证签名
  • 如果 ID Token 是通过客户端和 Token 端点的直接通信接收的(此流程中是这样),可以使用 TLS 服务器验证代替检查 Token 签名。
  • 在其他情况下,客户端必须根据 JWS [JWS] 规范,使用 alg 头参数中指定的算法验证 Token 签名:
    • 客户端必须使用发行方提供的密钥。
    • alg 的默认值应为 RS256,或在注册期间由 id_token_signed_response_alg 参数指定的算法。
    • 如果使用基于 MAC 的算法(如 HS256HS384HS512),则使用 client_secret 的 UTF-8 表示作为密钥验证签名。
    • 如果 aud 声明有多个值,对于基于 MAC 的算法,行为未指定。

  1. 验证时间相关声明
  • 当前时间必须在 exp (过期时间) 声明表示的时间之前。
  • iat (签发时间) 声明可以用于拒绝签发时间过早的 Token,以限制存储 nonce 的时间,防止攻击。允许的时间范围由客户端决定。

  1. 验证 nonce (一次性标识符)
  • 如果在身份验证请求中发送了 nonce 值,则 ID Token 必须包含 nonce 声明,其值必须与发送的值一致。
  • 客户端应检查 nonce 的值,以防止重放攻击(具体检测方法由客户端决定)。

  1. 验证 acr (身份验证上下文类参考)
  • 如果请求了 acr 声明,客户端应检查其值是否适当。acr 的含义和处理超出本规范范围。

  1. 验证 auth_time (身份验证时间)
  • 如果通过请求 auth_time 声明或使用 max_age 参数请求了身份验证时间:
    • 客户端应检查 auth_time 声明的值。
    • 如果客户端认为自上次用户身份验证以来时间过长,应请求重新验证。

通过上述步骤,客户端可以确保 ID Token 的完整性、真实性及其与预期的身份验证过程一致性。

3.1.3.8. 访问令牌验证

在使用授权码流(Authorization Code Flow)时,如果 ID Token 包含 at_hash 声明,客户端可以按照隐式流(Implicit Flow)中第 3.2.2.9 节定义的方式验证访问令牌,但使用从令牌端点(Token Endpoint)返回的 ID Token 和访问令牌进行验证。

3.2. 使用隐式流进行认证

本节描述了如何使用隐式流(Implicit Flow)进行认证。在隐式流中,所有令牌均从授权端点(Authorization Endpoint)返回;不使用令牌端点(Token Endpoint)。

隐式流主要用于通过脚本语言在浏览器中实现的客户端。访问令牌(Access Token)和 ID Token 直接返回给客户端,这可能会将它们暴露给最终用户(End-User)以及能够访问最终用户代理(User Agent)的应用程序。此外,授权服务器不会执行客户端认证(Client Authentication)。

3.2.1. 隐式流步骤

隐式流遵循以下步骤:

  1. 客户端准备一个包含所需请求参数的认证请求(Authentication Request)。
  2. 客户端将请求发送到授权服务器(Authorization Server)。
  3. 授权服务器对最终用户(End-User)进行认证。
  4. 授权服务器获取最终用户的同意/授权。
  5. 授权服务器将最终用户重定向回客户端,并附带 ID Token,以及(如果请求了)访问令牌(Access Token)。
  6. 客户端验证 ID Token,并检索最终用户的主体标识符(Subject Identifier)。

3.2.2. 授权端点

在使用隐式流(Implicit Flow)时,授权端点(Authorization Endpoint)的使用方式与授权码流(Authorization Code Flow)相同,如第 3.1.2 节所定义的内容,但本节中指定的差异除外。

3.2.2.1. 认证请求

认证请求按第 3.1.2.1 节中定义进行,但以下参数需按如下方式使用:

response_type
必需。OAuth 2.0 的响应类型值,确定使用的授权处理流,包括从端点返回的参数。在隐式流中,该值为 id_token tokenid_token。这两个值的含义在 [OAuth.Responses] 中定义。如果值为 id_token,则不会返回访问令牌(Access Token)。
注意:虽然 OAuth 2.0 也定义了用于隐式流的 token 响应类型值,但 OpenID Connect 不使用此响应类型,因为该类型不会返回 ID Token。

redirect_uri
必需。响应将发送到的重定向 URI。此 URI 必须与客户端在 OpenID 提供方(OP)处预注册的重定向 URI 值之一完全匹配,匹配方式按照 [RFC3986] 第 6.2.1 节(简单字符串比较)进行。当使用此流时,除非客户端是本地应用程序,否则重定向 URI 不得使用 http 协议方案;若为本地应用程序,则可以使用 http,并使用 localhost 或 IP 环回地址(127.0.0.1 或 [::1])作为主机名。

nonce
必需。一个字符串值,用于将客户端会话与 ID Token 关联,并缓解重放攻击。该值从认证请求传递到 ID Token,保持不变。nonce 值必须具有足够的熵,以防止攻击者猜测其值。有关实现注意事项,请参阅第 15.5.2 节。


以下是使用隐式流的非规范示例请求,该请求由用户代理(User Agent)发送到授权服务器,此请求响应于客户端的 HTTP 302 重定向响应(为了显示目的,值内包含换行符):

GET /authorize? response_type=id_token%20token &client_id=s6BhdRkqt3 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb &scope=openid%20profile &state=af0ifjsldkj &nonce=n-0S6_WzA2Mj HTTP/1.1 Host: server.example.com
3.2.2.2. 认证请求校验

在使用隐式流(Implicit Flow)时,认证请求的校验方式与授权码流(Authorization Code Flow)的校验方式相同,具体校验过程参见第 3.1.2.2 节。

3.2.2.3. 授权服务器认证终端用户

在使用隐式流(Implicit Flow)时,终端用户的认证方式与授权码流(Authorization Code Flow)的认证方式相同,具体认证过程参见第 3.1.2.3 节。

3.2.2.4. 授权服务器获取终端用户同意/授权

在使用隐式流(Implicit Flow)时,终端用户同意的获取方式与授权码流(Authorization Code Flow)相同,具体获取过程参见第 3.1.2.4 节。

3.2.2.5. 成功的身份验证响应

在使用隐式流(Implicit Flow)时,身份验证响应的方式与授权码流(Authorization Code Flow)相同,具体参见第 3.1.2.5 节,除非本节指定了其他不同之处。

在使用隐式流时,所有响应参数都被添加到重定向 URI 的 fragment 部分,如 OAuth 2.0 多响应类型编码实践 [OAuth.Responses] 中所述,除非指定了不同的响应模式。

以下是从授权端点返回的响应参数:

  • access_token
    OAuth 2.0 访问令牌。除非使用的 response_type 值是 id_token,否则会返回此参数。

  • token_type
    OAuth 2.0 令牌类型值。值必须是 Bearer 或客户端与授权服务器协商的其他 token_type 值。实现此配置文件的客户端必须支持 OAuth 2.0 Bearer 令牌使用规范 [RFC6750]。本配置文件仅描述使用 Bearer 令牌。

  • id_token
    必须返回 ID 令牌。

  • state
    OAuth 2.0 state 值。如果授权请求中包含 state 参数,则此参数为必填项。客户端必须验证 state 值与授权请求中的 state 参数值相等。

  • expires_in
    可选项。访问令牌的过期时间,单位为从响应生成时起的秒数。

根据 OAuth 2.0 [RFC6749] 的第 4.2.2 节,使用隐式流时不会返回代码结果。

以下是使用隐式流成功响应的非规范示例(为便于显示,值内有换行):

HTTP/1.1 302 Found Location: https://client.example.org/cb# access_token=SlAV32hkKG &token_type=bearer &id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso &expires_in=3600 &state=af0ifjsldkj
3.2.2.6. 身份验证错误响应

在使用隐式流(Implicit Flow)时,授权错误响应的方式与授权码流(Authorization Code Flow)相同,具体参见第 3.1.2.6 节,除非本节指定了其他不同之处。

每当返回错误响应参数时(例如当用户拒绝授权或用户身份验证失败时),授权服务器必须将错误授权响应以 fragment 组件的形式返回到重定向 URI 中,如 OAuth 2.0 [RFC6749] 第 4.2.2.1 节和 OAuth 2.0 多响应类型编码实践 [OAuth.Responses] 中所述,除非指定了不同的响应模式。

3.2.2.7. 重定向 URI Fragment 处理

由于响应参数是在重定向 URI 的 fragment 部分返回的,客户端需要让用户代理解析这些经过编码的 fragment 值,并将其传递给客户端的处理逻辑进行处理。有关 URI fragment 处理的实现说明,请参见第 15.5.3 节。

3.2.2.8. 身份验证响应验证

在使用隐式流时,客户端必须按照以下方式验证响应:

  1. 验证响应是否符合 [OAuth.Responses] 第 5 节的规定。
  2. 遵循 RFC 6749 的验证规则,特别是第 4.2.2 和第 10.12 节。
  3. 遵循第 3.2.2.11 节中的 ID Token 验证规则。
  4. 遵循第 3.2.2.9 节中的 Access Token 验证规则,除非使用的 response_type 值为 id_token
3.2.2.9. 访问令牌验证

为了验证从授权端点颁发并带有 ID Token 的访问令牌,客户端应执行以下操作:

  1. 使用 ID Token 的 JOSE 头部中 alg 标头参数指定的哈希算法,对访问令牌的 ASCII 表示形式的字节进行哈希处理。比如,如果 alg 为 RS256,则使用 SHA-256 作为哈希算法。
  2. 取哈希值的左半部分,并进行 base64url 编码。
  3. ID Token 中的 at_hash 值必须与上一步中生成的值匹配。
3.2.2.10. ID Token

ID Token 的内容如第 2 节所述。当使用隐式流程时,以下 ID Token 声明要求额外适用:

  • nonce
    对于此流程,nonce 声明是必需的。

  • at_hash
    访问令牌的哈希值。其值是访问令牌(access_token)的 ASCII 表示形式字节的哈希值左半部分的 base64url 编码,哈希算法使用的是 ID Token 的 JOSE 头部中的 alg 标头参数所指定的哈希算法。例如,如果 alg 是 RS256,则使用 SHA-256 哈希算法对 access_token 进行哈希,然后取哈希值的左 128 位并进行 base64url 编码。at_hash 是区分大小写的字符串。

    如果 ID Token 是从授权端点发出的,并且包含 access_token 值(对于 response_type 值为 id_token token 的情况),则这是必需的;如果没有颁发访问令牌(即 response_type 值为 id_token),则不应使用此值。

    3.2.2.11. ID Token Validation

当使用隐式流程时,ID Token 的内容必须以与授权码流程相同的方式进行验证,如第 3.1.3.7 节所定义,但本节指定了以下不同之处:

  • 客户端必须根据 JWS [JWS] 使用 JOSE 头部中的 alg 标头参数指定的算法来验证 ID Token 的签名。

  • 必须检查 nonce 声明的值,以确保其与在身份验证请求中发送的值相同。客户端应检查 nonce 值以防止重放攻击。检测重放攻击的精确方法由客户端决定。

3.3. 使用混合流进行认证

本节描述了如何使用混合流进行认证。在使用混合流时,某些令牌从授权端点返回,其他令牌则从令牌端点返回。混合流中令牌返回的机制在 OAuth 2.0 多重响应类型编码实践 [OAuth.Responses] 中进行了说明。

3.3.1. 混合流步骤

混合流遵循以下步骤:

  1. 客户端准备认证请求
    客户端创建一个包含所需请求参数的认证请求。

  2. 客户端向授权服务器发送请求
    客户端将请求发送到授权服务器。

  3. 授权服务器认证终端用户
    授权服务器对终端用户进行认证。

  4. 授权服务器获取终端用户的同意/授权
    授权服务器从终端用户获取同意或授权。

  5. 授权服务器将终端用户重定向回客户端
    授权服务器将终端用户重定向回客户端,同时返回授权码以及(根据响应类型)一个或多个附加参数。

  6. 客户端使用授权码向令牌端点请求响应
    客户端向令牌端点发送请求,并使用之前获取的授权码。

  7. 客户端接收包含 ID Token 和访问令牌的响应
    客户端在响应体中接收 ID Token 和访问令牌。

  8. 客户端验证 ID Token
    客户端验证 ID Token 并检索终端用户的主体标识符(Subject Identifier)。

    3.3.2. 授权端点

在使用混合流时,授权端点的使用方式与授权码流(详见第 3.1.2 节)基本相同,但本节中列出了一些不同之处。

3.3.2.1. 身份验证请求

身份验证请求的生成方式与第 3.1.2.1 节中定义的一致,但以下身份验证请求参数有如下使用方式:

response_type
必填。OAuth 2.0 的响应类型值,用于确定所使用的授权处理流程,包括从端点返回的参数。在混合流中,此值可以是 code id_tokencode tokencode id_token token。这些值的含义由 OAuth 2.0 多重响应类型编码实践 [OAuth.Responses] 定义。

nonce
如果请求的响应类型为 code id_tokencode id_token token,则必填;如果响应类型为 code token,则为可选。这是一个字符串值,用于将客户端会话与 ID Token 关联,并减轻重放攻击的风险。该值会从身份验证请求传递到 ID Token 中,且必须保持不变。nonce 值应具有足够的熵,以防止攻击者猜测其值。有关实现说明,请参阅第 15.5.2 节。


以下是使用混合流的一个非规范示例请求,用户代理会在客户端发送的对应 HTTP 302 重定向响应后,将该请求发送到授权服务器(为了显示方便,对值进行了换行处理):

GET /authorize? response_type=code%20id_token &client_id=s6BhdRkqt3 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb &scope=openid%20profile%20email &nonce=n-0S6_WzA2Mj &state=af0ifjsldkj HTTP/1.1 Host: server.example.com
3.3.2.2. 身份验证请求验证

在使用混合流时,身份验证请求的验证方式与授权码流中定义的验证方式相同,如第 3.1.2.2 节所述。

3.3.2.3. 授权服务器对终端用户进行身份验证

在使用混合流时,终端用户的身份验证与授权码流中的方式相同,如第 3.1.2.3 节所述。

3.3.2.4. 授权服务器获取终端用户的同意/授权

在使用混合流时,终端用户的同意或授权的获取方式与授权码流中的方式相同,如第 3.1.2.4 节所述。

3.3.2.5. 成功的认证响应

在使用混合流时,认证响应的生成方式与隐式流中的方式相同,如第 3.2.2.5 节所述,但以下区别除外。

授权端点的结果使用方式:

  1. access_token

    • OAuth 2.0 访问令牌。
    • response_type 值为 code tokencode id_token token 时返回。
    • 在这些情况下,还会返回 token_type 值。
  2. id_token

    • ID 令牌。
    • response_type 值为 code id_tokencode id_token token 时返回。
  3. code

    • 授权码。
    • 在使用混合流时,总是返回授权码。

非规范性示例:
以下是使用混合流的成功响应的示例(为便于显示,内容进行了换行处理):

HTTP/1.1 302 Found Location: https://client.example.org/cb# code=SplxlOBeZQQYbYS6WxSbIA &id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso &state=af0ifjsldkj
3.3.2.6. 认证错误响应

在使用混合流时,授权错误响应的生成方式与授权码流相同,如第 3.1.2.6 节所述,但需注意以下区别。

错误响应参数的返回方式:

  • 当返回错误响应参数时(例如,最终用户拒绝授权或认证失败),授权服务器 必须 将错误的授权响应包含在重定向 URI 的 片段组件 中。
  • 这种行为遵循 OAuth 2.0 [RFC6749] 第 4.2.2.1 节 和 OAuth 2.0 多响应类型编码实践 [OAuth.Responses] 的定义。
  • 如果指定了不同的响应模式,则使用该指定模式。

这种机制确保了混合流中的错误处理与隐式流类似,但仍遵循特定的混合流要求。

3.3.2.7. 重定向 URI 片段处理

在使用混合流时,对重定向 URI 片段参数的处理要求与隐式流相同,如第 3.2.2.7 节所定义。此外,可以参考第 15.5.3 节中的 URI 片段处理实现注意事项。

要点总结:

  1. 参数处理
    混合流的片段参数(如 access_tokenid_token)返回后,客户端需要解析 URI 的片段部分并将其传递给客户端的处理逻辑以进行消费。

  2. 一致性
    这种处理方式与隐式流保持一致,以确保在解析重定向 URI 片段时不会出现不兼容问题。

  3. 安全性建议
    在实现过程中,开发人员应关注安全性和防护措施,例如确保片段参数没有被意外泄露或篡改。

    3.3.2.8. 认证响应验证

在使用混合流时,客户端必须按以下方式验证响应:

  1. 符合规范
    确保响应符合 [OAuth.Responses] 第 5 节的要求。

  2. 遵循 RFC 6749 验证规则
    按照 RFC 6749 中的验证规则,特别是第 4.2.2 节和第 10.12 节的规则进行验证。

  3. ID Token 验证
    当使用的 response_typecode id_tokencode id_token token 时,遵循第 3.3.2.12 节中的 ID Token 验证规则。

  4. Access Token 验证
    当使用的 response_typecode tokencode id_token token 时,遵循第 3.3.2.9 节中的 Access Token 验证规则。

  5. 授权码验证
    当使用的 response_typecode id_tokencode id_token token 时,遵循第 3.3.2.10 节中的授权码验证规则。

要点总结:

  • 客户端需要根据不同的 response_type 来分别验证 ID Token、Access Token 和授权码。
  • 验证步骤必须严格遵守 OAuth 规范中的相关要求,确保每个令牌的安全性和有效性。
3.3.2.9. Access Token 验证

在使用混合流时,从授权端点返回的 Access Token 验证与使用隐式流时相同,按照第 3.2.2.9 节中的定义进行。

验证步骤概述:

  1. 哈希验证
    对 Access Token 进行哈希运算,使用 ID Token 的 JOSE Header 中的 alg 值指定的哈希算法(例如,如果 alg 为 RS256,则使用 SHA-256 哈希算法)。

  2. 生成 at_hash
    生成的哈希值取左半部分,然后进行 Base64URL 编码。

  3. 匹配 at_hash
    ID Token 中的 at_hash 值必须与上述步骤中计算出的值匹配。

此过程确保了 Access Token 的完整性和有效性,并防止了令牌篡改和重放攻击。

3.3.2.10 授权码验证

要验证授权端点签发的授权码与 ID Token,客户端应当执行以下步骤:

  1. 对授权码进行哈希处理
    使用 ID Token 的 JOSE Header 中 alg 参数指定的哈希算法对授权码的 ASCII 表示形式的字节数据进行哈希。例如,如果 algRS256,则使用的哈希算法为 SHA-256

  2. 取哈希值的左半部分并进行编码
    从计算出的哈希值中取最左侧的一半部分,并将其进行 Base64URL 编码。

  3. 验证 c_hash
    如果 ID Token 中包含 c_hash,则 c_hash 的值必须与前一步计算出的值相匹配。

    3.3.2.11 ID Token

ID Token 的内容如第 2 节所述。在使用混合流程(Hybrid Flow)时,授权端点返回的 ID Token 需要满足以下额外要求:


  1. nonce
  • 用途:防止重放攻击。
  • 要求
    • 如果身份验证请求中包含 nonce 参数,则授权服务器必须在 ID Token 中包含 nonce 声明。

  1. at_hash
  • 定义:访问令牌 (access_token) 的哈希值。
  • 计算方法
    1. 使用 ID Token 的 JOSE Header 中 alg 参数指定的哈希算法对 access_token 的 ASCII 表示形式的字节数据进行哈希。
      • 例如,如果 algRS256,则使用 SHA-256access_token 进行哈希。
    2. 取哈希值的左侧 128 位(即哈希值的前一半)。
    3. 将这部分结果进行 Base64URL 编码。
  • 结果at_hash 是一个大小写敏感的字符串。
  • 要求
    • 如果 ID Token 是通过授权端点与 access_token 一起签发的(如 response_typecode id_token token),则 at_hash必需的
    • 否则,它是可选的

  1. c_hash
  • 定义:授权码 (code) 的哈希值。
  • 计算方法
    1. 使用 ID Token 的 JOSE Header 中 alg 参数指定的哈希算法对授权码的 ASCII 表示形式的字节数据进行哈希。
      • 例如,如果 algHS512,则使用 SHA-512 对授权码进行哈希。
    2. 取哈希值的左侧 256 位(即哈希值的前一半)。
    3. 将这部分结果进行 Base64URL 编码。
  • 结果c_hash 是一个大小写敏感的字符串。
  • 要求
    • 如果 ID Token 是通过授权端点与授权码一起签发的(如 response_typecode id_tokencode id_token token),则 c_hash必需的
    • 否则,它是可选的
3.3.2.12 ID Token 验证

在使用混合流程(Hybrid Flow)时,从授权端点返回的 ID Token 的内容必须按照隐式流程(Implicit Flow)的方式进行验证,具体验证方法详见第 3.2.2.11 节。


隐式流程中 ID Token 验证的关键步骤:

  1. 验证签名
    使用授权服务器的公钥验证 ID Token 的签名,确保其未被篡改。

  2. 检查 iss (Issuer) 声明
    确保 iss 值与预期的授权服务器标识符一致。

  3. 检查 aud (Audience) 声明
    确保 aud 包含客户端的 client_id

  4. 检查 nonce
    如果身份验证请求中包含 nonce 参数,则必须在 ID Token 中包含 nonce,且其值应与请求中的值一致。

  5. 检查过期时间 (exp) 和签发时间 (iat)
    确保:

    • 当前时间未超过 exp 声明的过期时间。
    • iat 声明的签发时间合理,防止过期或不可信的 Token 被接受。
  6. 验证 at_hashc_hash(如果适用)

    • 如果 ID Token 包含 at_hashc_hash
      • 计算访问令牌或授权码的哈希值。
      • 确保这些值与 ID Token 中的 at_hashc_hash 匹配。

通过这些验证步骤,可以确保从授权端点返回的 ID Token 是可信的并符合协议规范。

3.3.3 Token 端点

在使用混合流程(Hybrid Flow)时,Token 端点的使用方式与授权码流程(Authorization Code Flow)相同(详见第 3.1.3 节),但本节中指定的差异除外。

3.3.3.1 Token 请求

在使用混合流程(Hybrid Flow)时,Token 请求的方式与授权码流程(Authorization Code Flow)相同(详见第 3.1.3.1 节)。

3.3.3.2 Token 请求验证

在使用混合流程(Hybrid Flow)时,Token 请求的验证方式与授权码流程(Authorization Code Flow)相同(详见第 3.1.3.2 节)。

3.3.3.3 成功的 Token 响应

在使用混合流程(Hybrid Flow)时,Token 响应的方式与授权码流程(Authorization Code Flow)相同(详见第 3.1.3.3 节)。

3.3.3.4 Token 错误响应

在使用混合流程(Hybrid Flow)时,Token 错误响应的方式与授权码流程(Authorization Code Flow)相同(详见第 3.1.3.4 节)。


3.3.3.5 Token 响应验证

在使用混合流程(Hybrid Flow)时,Token 响应的验证方式与授权码流程(Authorization Code Flow)相同(详见第 3.1.3.5 节)。

3.3.3.6 ID Token

在使用混合流程(Hybrid Flow)时,从 Token 端点返回的 ID Token 内容与从 授权端点返回的 ID Token 内容相同(详见第 3.3.2.11 节),但本节中指定的差异除外。


主要要求:

  1. isssub 声明一致性

    • 如果从授权端点和 Token 端点都返回了 ID Token(如 response_typecode id_tokencode id_token token),则两者中的 isssub 声明的值必须相同
  2. 身份验证事件的声明

    • 任何在一个 ID Token 中出现的关于身份验证事件的声明,应当在两个 ID Token 中都出现。
  3. 关于终端用户的声明

    • 如果两个 ID Token 都包含关于终端用户的声明,且这些声明在两个 ID Token 中都有出现,则这些声明的值应当相同
    • 注意:授权端点可能会根据隐私原因返回更少的关于终端用户的声明。
  4. at_hashc_hash 声明

    • 即使在从授权端点返回的 ID Token 中存在 at_hashc_hash 声明,Token 端点返回的 ID Token 也可以省略这些声明
    • 这是因为从 Token 端点返回的 ID Token 和访问令牌(Access Token)已经通过 Token 端点执行的 TLS 加密进行了加密绑定,因此不需要额外的哈希声明。
    3.3.3.7 ID Token 验证

在使用混合流程(Hybrid Flow)时,从 Token 端点返回的 ID Token 的内容必须按照授权码流程(Authorization Code Flow)中的 ID Token 验证方式进行验证,具体验证方法详见第 3.1.3.7 节。

3.3.3.8 访问令牌

如果 访问令牌(Access Token) 同时从 授权端点Token 端点 返回(如 response_typecode tokencode id_token token),它们的值可以相同,也可以不同

  • 原因:由于授权端点和 Token 端点具有不同的安全特性,因此可能会返回不同的访问令牌。

  • 差异

    • 两个端点返回的访问令牌的生命周期可能不同。
    • 每个令牌可能授予不同的资源访问权限。
    3.3.3.9 访问令牌验证

在使用混合流程(Hybrid Flow)时,从 Token 端点 返回的 访问令牌(Access Token) 必须按照授权码流程(Authorization Code Flow)中的访问令牌验证方式进行验证,具体验证方法详见第 3.1.3.8 节。

4. 从第三方发起登录

在某些情况下,登录流程是由 OpenID 提供者(OP)或其他方发起的,而不是由依赖方(RP)发起。在这种情况下,发起方会重定向到 RP 的登录发起端点,要求 RP 向指定的 OP 发送身份验证请求。需要注意的是,这个登录发起端点可以是 RP 的其他页面,而不是 RP 的默认着陆页。支持 OpenID Connect 动态客户端注册 1.0 [OpenID.Registration] 的 RP 使用 initiate_login_uri 注册参数注册该端点值。

发起登录请求的一方通过重定向到 RP 的登录发起端点,传递以下参数:

参数说明:

  • iss
    必需。RP 需要将身份验证请求发送到的 OP 的发行者标识符。其值必须是使用 https 协议的 URL。

  • login_hint
    可选。向授权服务器提供关于待验证终端用户的提示。该参数的字符串值的含义由 OP 自行决定。在常见的用例中,该值通常是 RP 在请求身份验证时收集的电子邮件地址、电话号码或用户名。例如,RP 在请求身份验证之前询问终端用户其电子邮件地址(或其他标识符),并将该标识符作为提示传递给 OpenID 提供者。建议提示值与发现过程中提供的值一致。其他使用场景包括使用 ID Token 中的 sub 声明作为提示值,或可能使用关于请求身份验证的其他信息。

  • target_link_uri
    可选。RP 在身份验证后请求重定向到的 URL。RP 必须验证 target_link_uri 的值,以防止被用作对外部站点的开放重定向器。

传输方式:

这些参数可以通过以下两种方式之一进行传递:

  1. 使用 HTTP GET 方法作为查询参数。
  2. 使用 HTML 表单值,通过自动提交的用户代理通过 HTTP POST 方法传递。

其他参数:

如果由扩展定义了其他参数,也可以发送这些参数。任何客户端无法理解的参数必须被忽略。

安全性建议:

客户端应采取框架打破(frame busting)等技术,防止终端用户在不知情的情况下通过点击劫持等攻击被第三方网站登录。有关更多详细信息,请参阅 [RFC6819] 的第 4.4.1.9 节。

5. 声明 (Claims)

本节说明客户端如何获取关于终端用户和身份验证事件的声明。它还定义了一组标准的基本个人资料声明。可以通过特定的作用域值请求预定义的声明集,或者使用 claims 请求参数单独请求每个声明。这些声明可以直接来自 OpenID 提供者(OP),也可以来自分布式源。

5.1. 标准声明 (Standard Claims)

本规范定义了一组标准声明。可以请求在用户信息响应(第5.3.2节)或ID令牌(第2节)中返回它们。

成员 (Member)类型 (Type)描述 (Description)
sub字符串 (string)终端用户在发行者处的标识符。
name字符串 (string)终端用户的全名,包括所有的姓名部分,可能包括头衔和后缀,按照终端用户的地区和偏好顺序排列。
given_name字符串 (string)终端用户的名字或名。注意,在某些文化中,人们可能有多个名字;所有名字可以出现,名字之间用空格分隔。
family_name字符串 (string)终端用户的姓氏。注意,在某些文化中,人们可能有多个姓氏或没有姓氏;所有姓氏可以出现,姓氏之间用空格分隔。
middle_name字符串 (string)终端用户的中间名。注意,在某些文化中,人们可能有多个中间名;所有中间名可以出现,名字之间用空格分隔。同时在某些文化中,中间名可能不使用。
nickname字符串 (string)终端用户的昵称,可能与给定名称相同。例如,昵称 “Mike” 可能与给定名称 “Michael” 一起返回。
preferred_username字符串 (string)终端用户希望在RP中使用的简称,例如janedoe或j.doe。此值可以是任何有效的JSON字符串,包括如@、/或空格等特殊字符。RP不得依赖此值唯一性,如第5.7节所述。
profile字符串 (string)终端用户的个人资料页面的URL。该网页内容应该是关于终端用户的。
picture字符串 (string)终端用户的个人资料图片的URL。该URL必须指向图像文件(例如PNG、JPEG或GIF),而不是包含图像的网页。注意,此URL应该特别引用适合描述终端用户的个人资料照片,而不是终端用户拍摄的任意照片。
website字符串 (string)终端用户的网页或博客的URL。该网页应包含终端用户或终端用户所属的组织发布的信息。
email字符串 (string)终端用户的首选电子邮件地址。其值必须符合RFC 5322 [RFC5322] 地址规范。RP不得依赖该值的唯一性,如第5.7节所述。
email_verified布尔值 (boolean)如果终端用户的电子邮件地址已验证,则为真,否则为假。当此声明值为真时,表示OP采取了积极措施,确保该电子邮件地址在验证时由终端用户控制。电子邮件验证的方式取决于所依赖的信任框架或合同协议。
gender字符串 (string)终端用户的性别。该规范定义的值为female和male。其他值可以在这两个值不适用时使用。
birthdate字符串 (string)终端用户的生日,使用ISO 8601-1 [ISO8601‑1] YYYY-MM-DD格式表示。年份可以是0000,表示省略年份。仅表示年份时,可以使用YYYY格式。注意,根据底层平台的日期处理函数,仅提供年份可能会导致不同的月份和日期,因此实施者需要考虑这个因素来正确处理日期。
zoneinfo字符串 (string)IANA时区数据库 [IANA.time‑zones] 中表示终端用户时区的字符串。例如,Europe/Paris或America/Los_Angeles。
locale字符串 (string)终端用户的语言环境,表示为BCP47 [RFC5646] 语言标签。通常为ISO 639 Alpha-2 [ISO639] 语言代码的小写形式和ISO 3166-1 Alpha-2 [ISO3166‑1] 国家代码的大写形式,中间用短横线分隔。例如en-US或fr-CA。作为兼容性说明,一些实现使用下划线作为分隔符,例如en_US;RP可以选择接受这种语言环境语法。
phone_number字符串 (string)终端用户首选的电话号码。推荐使用E.164 [E.164] 格式,例如+1 (425) 555-1212或+56 (2) 687 2400。如果电话号码包含分机号,建议使用RFC 3966 [RFC3966] 扩展语法表示,例如+1 (604) 555-1234;ext=5678。
phone_number_verified布尔值 (boolean)如果终端用户的电话号码已验证,则为真,否则为假。当此声明值为真时,表示OP采取了积极措施,确保该电话号码在验证时由终端用户控制。电话号码的验证方式取决于信任框架或合同协议。若为真,phone_number声明必须为E.164格式,任何扩展必须采用RFC 3966格式。
addressJSON 对象 (JSON object)终端用户首选的邮政地址。address成员的值是一个JSON结构,包含第5.1.1节定义的部分或全部成员。
updated_at数字 (number)终端用户信息上次更新的时间。其值是一个表示自1970-01-01T00:00:00Z(UTC)以来的秒数的JSON数字。

表1:已注册成员定义

5.1.1. 地址声明 (Address Claim)

地址声明表示一个物理邮寄地址。实现可以根据可用信息和终端用户的隐私偏好返回地址的部分字段。例如,可能仅返回国家和地区,而不返回更详细的地址信息。

实现可以选择以下几种方式来返回地址:

  1. 仅返回格式化的完整地址作为一个字符串。
  2. 仅返回单独的组件字段。
  3. 返回两者,格式化地址表示如何组合各个组件字段。

以下是地址声明中定义的所有字段,它们都表示为JSON字符串。

字段 (Field)描述 (Description)
formatted格式化的完整邮寄地址,适用于显示或用作邮寄标签。该字段可以包含多行,行与行之间用换行符分隔。换行符可以是回车符/换行符对(“\r\n”)或单个换行符(“\n”)。
street_address完整的街道地址组件,可能包括门牌号、街道名称、邮政信箱和多行扩展街道地址信息。该字段也可以包含多行,行与行之间用换行符分隔。换行符可以是回车符/换行符对(“\r\n”)或单个换行符(“\n”)。
locality城市或地方组件。
region州、省、县或地区组件。
postal_code邮政编码组件。
country国家名称组件。

5.1.2. 额外声明 (Additional Claims)

尽管本规范仅定义了一小部分声明作为标准声明,但可以与标准声明一起使用其他声明。在使用这些声明时,建议使用具有抗碰撞性的名称来命名声明,如在JSON Web Token (JWT) [JWT] 规范中所述。或者,在命名冲突不太可能发生的情况下,可以安全地使用私有声明名称,正如JWT规范中所描述的那样。或者,如果特定的额外声明具有广泛和普遍的适用性,它们可以根据JWT规范通过注册的声明名称进行注册。

5.2. 声明语言和脚本 (Claims Languages and Scripts)

可读的声明值和引用可读值的声明值可以用多种语言和脚本表示。为了指定语言和脚本,使用BCP47 [RFC5646] 语言标签,并将其添加到成员名称中,标签之间使用 # 字符分隔。例如,family_name#ja-Kana-JP 表示使用日语片假名表示的姓氏,这通常用于索引和表示与同名的汉字表示形式的语音,而 family_name#ja-Hani-JP 则是其汉字表示形式的音标表示。

同样,websitewebsite#de 可以返回,其中一个表示一个网站的通用语言版本,另一个则是德语版本。

由于声明名称是区分大小写的,强烈建议语言标签值在声明名称中以与其在IANA语言子标签注册表 [IANA.Language] 中注册时相同的字符大小写形式书写。特别地,语言名称通常使用小写字符,地区名称使用大写字符,脚本使用混合大小写字符。然而,由于BCP47语言标签值是大小写不敏感的,实现应当以不区分大小写的方式解析提供的语言标签值。

根据BCP47中的建议,声明的语言标签值应该尽可能具体。例如,许多情况下,使用 fr 就足够了,而不是 fr-CAfr-FR。在可能的情况下,OP应尽量将请求的声明语言标签与其拥有的声明相匹配。例如,如果客户端请求使用 de(德语)语言标签,而OP拥有标记为 de-CH(瑞士德语)的值且没有通用德语值,那么OP返回瑞士德语值是适当的。(这有意将语言标签匹配的复杂性尽可能移至OP,以简化客户端的处理。)

OpenID Connect定义了以下授权请求参数,以启用指定返回声明所使用的首选语言和脚本:

  • claims_locales
    可选。表示End-User首选语言和脚本的声明,用空格分隔的BCP47 [RFC5646]语言标签值列表,并按优先顺序排列。如果某些或所有请求的语言区域不受OpenID提供者支持,则不应产生错误。

当OP通过 claims_locales 参数或其他方式确定End-User和客户端仅请求一种语言和脚本时,建议OP返回不带语言标签的声明,前提是它们采用此语言和脚本。还建议客户端编写时能够处理和利用带有语言标签的声明。

5.3. 用户信息端点 (UserInfo Endpoint)

用户信息端点是一个受OAuth 2.0保护的资源,返回经过认证的End-User的声明。为了获取有关End-User的请求声明,客户端使用通过OpenID Connect认证获得的访问令牌向用户信息端点发起请求。这些声明通常以JSON对象的形式表示,包含一系列名称和值对。

与用户信息端点的通信必须使用TLS协议。有关使用TLS的更多信息,请参见第16.17节。

  • UserInfo端点必须支持使用HTTP GET和HTTP POST方法,这些方法在RFC 7231 [RFC7231]中有定义。

  • UserInfo端点必须接受作为OAuth 2.0 Bearer令牌使用的访问令牌,符合OAuth 2.0 Bearer Token使用规范 [RFC6750]。

  • UserInfo端点应该支持使用跨域资源共享(CORS) [CORS]和/或其他方法,以便启用JavaScript客户端和其他基于浏览器的客户端访问。

5.3.1. UserInfo 请求**

客户端使用 HTTP GET 或 HTTP POST 方法发送 UserInfo 请求。从 OpenID Connect 认证请求中获取的访问令牌(Access Token)必须作为 Bearer Token 发送,具体遵循 OAuth 2.0 Bearer Token 使用规范的第 2 节 [RFC6750]。

建议请求使用 HTTP GET 方法,并通过 Authorization 标头字段发送访问令牌。

以下是一个非规范性的 UserInfo 请求示例:

GET /userinfo HTTP/1.1 Host: server.example.com Authorization: Bearer SlAV32hkKG

5.3.2. 成功的 UserInfo 响应**

UserInfo 声明(Claims)必须作为 JSON 对象的成员返回,除非在客户端注册期间请求了签名或加密的响应。第 5.1 节定义的声明可以被返回,同时也可以包含未在其中定义的其他声明。

出于隐私原因,OpenID 提供者(OpenID Providers, OP)可以选择不返回某些请求的声明。不返回某个请求声明并不是错误条件。

如果某个声明未被返回,该声明的名称应从表示声明的 JSON 对象中省略;不应以 null 或空字符串值的形式存在。

在 UserInfo 响应中,sub(主体)声明必须始终返回。

**注意:**由于可能存在令牌替换攻击(见第 16.11 节),UserInfo 响应不能保证是关于由 ID Token 的 sub(主体)元素标识的终端用户(End-User)。UserInfo 响应中的 sub 声明必须经过验证,与 ID Token 中的 sub 声明完全匹配;如果二者不匹配,UserInfo 响应的值不得使用。

收到 UserInfo 请求后,除非在注册期间指定了不同的格式,UserInfo 端点(Endpoint)必须按第 13.3 节中的说明,以 JSON 序列化的形式在 HTTP 响应体中返回 UserInfo 响应。UserInfo 端点必须返回一个 content-type 头部以指示返回的格式。如果响应体是文本 JSON 对象,HTTP 响应的内容类型必须为 application/json;响应体应使用 UTF-8 编码。

如果 UserInfo 响应是签名和/或加密的,那么声明会被以 JWT(JSON Web Token)的形式返回,内容类型必须为 application/jwt。响应可以仅加密而不签名。如果同时请求签名和加密,响应必须先签名再加密,结果是一个嵌套的 JWT,如 [JWT] 中定义。

如果进行了签名,UserInfo 响应必须包含声明 iss(发行者)和 aud(受众)。iss 的值必须是 OP 的发行者标识符 URL;aud 的值必须是(或包含)RP(客户端)的 Client ID 值。

以下是一个非规范性的 UserInfo 响应示例:

HTTP/1.1 200 OK Content-Type: application/json { "sub": "248289761001", "name": "Jane Doe", "given_name": "Jane", "family_name": "Doe", "preferred_username": "j.doe", "email": "janedoe@example.com", "picture": "http://example.com/janedoe/me.jpg" }

5.3.3. UserInfo 错误响应**

当发生错误条件时,UserInfo 端点按照 [RFC6750] 第 3 节的定义返回错误响应。(与 RFC 6750 无关的 HTTP 错误会使用适当的 HTTP 状态代码返回给用户代理。)

以下是一个非规范性的 UserInfo 错误响应示例:

HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer error="invalid_token", error_description="The Access Token expired"

5.3.4. UserInfo 响应验证**

客户端必须按以下步骤验证 UserInfo 响应:

  1. 验证响应的 OP(OpenID 提供者)是否为预期的 OP:
    根据 RFC 6125 [RFC6125],通过 TLS 服务器证书检查确保响应来自预期的 OpenID 提供者。

  2. 解密 UserInfo 响应(如果适用):
    如果客户端在注册期间提供了 userinfo_encrypted_response_alg 参数,则使用注册期间指定的密钥解密 UserInfo 响应。

  3. 验证签名(如果适用):
    如果响应已签名,客户端应根据 JWS [JWS] 的要求验证签名。

5.4. 使用 Scope 值请求声明**

OpenID Connect 客户端使用 Scope 值(如 [RFC6749] 第 3.3 节中定义)来指定访问令牌所请求的访问权限。与访问令牌相关的 Scope 决定了在访问 OAuth 2.0 受保护端点时可以使用的资源。受保护资源端点可以根据请求的访问令牌所使用的 Scope 值和其他参数执行不同的操作并返回不同的信息。

在 OpenID Connect 中,Scope 可用于请求特定信息集作为声明(Claim)值。

以下 Scope 所请求的声明被授权服务器视为自愿声明(Voluntary Claims):

OpenID Connect 定义的 Scope 值及其请求的声明:

  1. profile
    可选
    此 Scope 值请求访问终端用户的默认个人资料声明,包括:
    name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, 和 updated_at

  2. email
    可选
    此 Scope 值请求访问 emailemail_verified 声明。

  3. address
    可选
    此 Scope 值请求访问 address 声明。

  4. phone
    可选
    此 Scope 值请求访问 phone_numberphone_number_verified 声明。

使用多个 Scope 值 多个 Scope 值可以通过创建一个用空格分隔的、区分大小写的 ASCII Scope 值列表来使用。

response_type 的值导致颁发访问令牌时,profileemailaddressphone Scope 值所请求的声明会通过 UserInfo 端点返回(见第 5.3.2 节)。但是,如果没有颁发访问令牌(例如 response_typeid_token 的情况),则结果声明会在 ID Token 中返回。

在某些情况下,终端用户可以选择让 OpenID 提供者拒绝提供部分或全部由 RP 请求的信息。为了最小化要求终端用户披露的信息量,RP 可以选择仅请求 UserInfo 端点可用信息的子集。

未编码的 Scope 请求示例:

scope=openid profile email phone

5.5. 使用 “claims” 请求参数请求声明**

OpenID Connect 定义了以下授权请求参数,用于请求特定的声明(Claims)以及指定适用于这些声明的参数:

参数说明

  • claims
    可选
    此参数用于请求返回特定的声明。它的值是一个 JSON 对象,列出所请求的声明。

功能描述

  1. claims 参数的用途

    • claims 参数允许从 UserInfo 端点和/或 ID Token 中请求特定的声明。
    • 它被表示为一个 JSON 对象,包含从这些位置请求的声明列表。
    • 还可以指定声明的属性。
  2. 支持情况

    • claims 参数的支持是可选的。
    • 如果 OpenID 提供者(OP)不支持该参数,但客户端(RP)使用了它,OP 应返回一组它认为对 RP 和终端用户有用的声明,具体选择由 OP 自行决定。
    • claims_parameter_supported 探索结果指示 OP 是否支持此参数。
  3. 编码与表示

    • 在 OAuth 2.0 请求中,claims 参数值表示为 UTF-8 编码的 JSON(在作为 OAuth 参数传递时,会经过表单编码)。
    • 如果在请求对象中使用(参见第 6.1 节),JSON 被用作 claims 成员的值。

Claims 请求 JSON 对象的顶级成员

  • userinfo
    可选
    请求从 UserInfo 端点返回列出的单个声明:

    • 如果存在,列表中的声明被请求添加到通过 Scope 值请求的任何声明中。
    • 如果不存在,则从 UserInfo 端点请求的声明仅限于通过 Scope 值请求的声明。
    • 要求: 如果使用 userinfo 成员,必须同时使用会导致为 UserInfo 端点颁发访问令牌的 response_type 值。
  • id_token
    可选
    请求在 ID Token 中返回列出的单个声明:

    • 如果存在,列表中的声明被请求添加到 ID Token 的默认声明中。
    • 如果不存在,仅请求默认的 ID Token 声明。
    • 默认声明的定义见第 2 节以及第 3.1.3.6、3.2.2.10、3.3.2.11 和 3.3.3.6 节中的各个流程要求。
  • 其他成员

    • 可能存在其他成员。
    • 未被理解的成员必须被忽略。

示例 Claims 请求

{ "userinfo": { "given_name": {"essential": true}, "nickname": null, "email": {"essential": true}, "email_verified": {"essential": true}, "picture": null, "http://example.info/claims/groups": null }, "id_token": { "auth_time": {"essential": true}, "acr": {"values": ["urn:mace:incommon:iap:silver"]} } }

说明

  1. 非标准声明
    示例中包含了一个不在第 5.1 节定义的标准声明集中的声明:http://example.info/claims/groups
    使用 claims 参数是请求 Scope 值无法指定的特定声明组合的唯一方法。

  2. 声明属性

    • "essential": true 指示该声明对 RP 是必需的。
    • "values" 列出了声明的可能值。

通过这种方式,RP 能够灵活地请求所需的声明,并控制返回数据的精确范围。

5.5.1. 请求个别声明 (Individual Claims Requests)

在使用 claims 参数时,userinfoid_token 成员均为 JSON 对象,其成员名称为请求的个别声明的名称。每个声明的成员值必须是以下之一:


支持的成员值

  1. null
    表示以默认方式请求该声明,特别是该声明为自愿声明(Voluntary Claim)。
    示例:
    "given_name": null
    上述请求以默认方式获取 given_name 声明。

  1. JSON 对象
    提供有关请求声明的附加信息。以下是支持的 JSON 对象成员:

    • essential (可选)
      指示声明是否为必要声明(Essential Claim)

      • 值为 true 表示该声明是必要的。
      • 值为 false 表示该声明是自愿的(默认值)。

      示例:

      "auth_time": {"essential": true}

      上述请求指定 auth_time 为必要声明。

      注意:
      即使必要声明不可用(例如用户未授权释放或声明不存在),除非特定声明描述中另有规定,授权服务器仍然不得因未返回声明而生成错误

    • value (可选)
      请求声明返回特定值。
      示例:

      "sub": {"value": "248289761001"}

      上述请求指定 sub 声明的值必须为 248289761001,该值通常用于识别特定用户。

      注意:

      • 如果请求的值与实际值不匹配,则响应中不会包含该声明。
      • 如果声明是 sub,值不匹配会导致认证失败(详见第 3.1.2.2 节)。
    • values (可选)
      请求声明返回一组可接受值之一,并按偏好顺序排列。
      示例:

      "acr": { "essential": true, "values": [ "urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze" ] }

      上述请求指定 acr 声明必须返回 urn:mace:incommon:iap:silverurn:mace:incommon:iap:bronze 中的一个值。

      注意:

      • 如果声明值与请求值之一不匹配,则响应中不会包含该声明。
    • 其他成员
      声明对象中可能包含其他自定义成员。

      如果客户端(RP)未理解这些成员,则必须忽略它们。


简化作用

  • 当支持 claims 参数时,第 5.4 节定义的 Scope 值可看作是请求声明集合的简写方法。

  • 例如,使用 Scope 值 openid email 和返回访问令牌的 response_type 等效于以下个别声明请求:

    等效于使用 email Scope 值:

    { "userinfo": { "email": null, "email_verified": null } }

通过支持 claims 参数,客户端可以灵活、细粒度地请求必要声明,从而更好地满足特定的业务需求和用户授权要求。

5.5.1.1. 请求 acr 声明 (Requesting the “acr” Claim)

acr (Authentication Context Class Reference) 声明表示认证上下文的等级或方法,客户端可通过不同方式请求它。以下是规则和行为细节:


请求 acr 声明作为必要声明 (Essential Claim)

  • 如果在 ID Token 中请求 acr 声明,且:

    • 设置了 essential: true
    • 使用了 valuevalues 参数指定特定的 acr 值,

    并且授权服务器支持 claims 参数,则:

    1. 必须返回匹配的 acr 声明值
      授权服务器必须返回一个与请求的 valuevalues 参数匹配的 acr 值。

    2. 可能要求用户重新认证
      如果需要满足所请求的 acr 声明值,授权服务器可以要求终端用户通过额外因素重新认证。

    3. 如果无法满足请求,认证失败
      如果这是一个必要声明,且授权服务器无法满足要求(例如用户拒绝提供额外认证),授权服务器必须将该情况视为认证失败。


请求 acr 声明作为自愿声明 (Voluntary Claim)

  • 如果未设置 essential: true 或通过 acr_values 参数请求,则:

    1. 可以返回当前会话的 acr
      授权服务器应返回当前会话的 acr 值,作为 acr 声明的值。

    2. 不要求返回 acr 声明
      如果声明是自愿的,授权服务器并非必须在响应中包含 acr 声明。


同时使用 acr_valuesacr 声明请求

  • 如果客户端同时:

    • 使用 acr_values 参数请求 acr 声明;
    • 在 ID Token 的 acr 声明中请求特定值,

    则此种情况下的行为未指定。授权服务器可能会根据实现进行处理,但协议未定义优先级或处理逻辑。


注意事项

  1. 必要声明失败的处理
    对于必要的 acr 声明,授权服务器如果无法满足要求,则必须终止认证并视为失败,而非简单返回默认值。

  2. 自愿声明的灵活性
    acr 声明是自愿的,授权服务器可灵活决定返回当前会话的上下文,或完全忽略该请求。


示例

必要声明请求

"id_token": { "acr": { "essential": true, "values": ["urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze"] } }

自愿声明请求

"id_token": { "acr": { "values": ["urn:mace:incommon:iap:bronze"] } }

在以上请求中:

  • 第一个示例表明 acr 声明是必要的,且必须匹配指定值之一。
  • 第二个示例则允许授权服务器返回匹配值或当前会话的默认值。

5.5.2. 针对单独声明的语言和脚本请求 (Languages and Scripts for Individual Claims)

OpenID Connect 支持为声明请求特定的语言和脚本,以便返回适合的人类可读值。以下是相关规则和行为说明:


指定语言和脚本

  • 通过带语言标签的声明名称
    在请求单独声明时,可以使用 # 分隔的 BCP47 [RFC5646] 语言标签 来指定请求的语言和脚本。

  • 声明名称语法
    声明名称的语法为 声明名称#语言标签,例如:

    • family_name#ja-Kana-JP
      表示请求日本片假名格式的姓氏。
    • family_name#ja-Hani-JP
      表示请求日本汉字格式的姓氏。
    • website#de
      表示请求德语版本的网站地址。

返回值行为

  1. 支持的语言和脚本
    如果授权服务器 (OP) 支持请求的语言和脚本,它应返回符合请求的声明值。

  2. 不支持的语言和脚本
    如果授权服务器无法提供符合请求语言和脚本的声明值:

    • 返回的声明值应使用语言标签标记。
      例如,如果请求了 family_name#ja-Kana-JP,但 OP 无法提供片假名格式的值,则返回其他语言/脚本版本时需附加语言标签。

示例

请求示例

{ "userinfo": { "family_name#ja-Kana-JP": {"essential": true}, "family_name#ja-Hani-JP": null, "website#de": null } }

响应示例

  • 支持请求的语言和脚本

    { "family_name#ja-Kana-JP": "タナカ", "family_name#ja-Hani-JP": "田中", "website#de": "https://beispiel.de" }
  • 不支持请求的语言和脚本
    如果 OP 无法提供片假名版本的姓氏,可能会返回:

    { "family_name#ja-Kana-JP": "Tanaka", "family_name#ja-Hani-JP": "田中", "website#de": "https://beispiel.de" }

    在这种情况下,family_name#ja-Kana-JP 使用了备用语言和脚本。


注意事项

  1. 可选性
    请求特定语言和脚本是可选的,OP 可以根据实现情况决定是否支持。

  2. 声明名称唯一性
    不同语言和脚本的声明使用唯一的名称格式(带语言标签的声明名称),避免歧义。

  3. 兼容性
    如果客户端未指定语言和脚本,则返回默认值。

    5.6. 声明类型 (Claim Types)

OpenID Connect 定义了三种声明值的表示方式,用于区分声明的来源和返回方式:


1. 普通声明 (Normal Claims)

  • 定义
    普通声明是直接由 OpenID 提供者 (OpenID Provider, OP) 声明的。
    即 OP 直接负责生成并返回这些声明值。

  • 支持要求
    必须支持 (MUST be supported)。
    这是 OpenID Connect 的核心功能,所有 OP 都需要实现。


2. 聚合声明 (Aggregated Claims)

  • 定义
    聚合声明由第三方声明提供者 (Claims Provider) 断言,但通过 OP 返回
    OP 会从第三方收集这些声明值,然后将它们整合并返回给客户端。

  • 支持要求
    可选支持 (OPTIONAL)。
    OP 可以根据需要选择是否实现此功能。


3. 分布式声明 (Distributed Claims)

  • 定义
    分布式声明同样由第三方声明提供者断言,但OP 返回的是引用 (references),而非直接返回声明值。
    客户端可以通过这些引用,从第三方声明提供者处获取声明值。

  • 支持要求
    可选支持 (OPTIONAL)。
    OP 可以根据需要选择是否实现此功能。


对比总结

声明类型描述支持要求
普通声明由 OP 直接生成和返回。必须支持 (MUST)
聚合声明第三方声明,由 OP 收集并返回。可选支持 (OPTIONAL)
分布式声明第三方声明,OP 返回引用,客户端需自行获取具体值。可选支持 (OPTIONAL)

使用场景

  1. 普通声明
    常用于 OP 自己管理用户信息的场景,例如 nameemail 等基本信息。

  2. 聚合声明
    适用于需要汇总多个数据源的信息,例如企业级系统整合多个身份数据。

  3. 分布式声明
    适用于敏感信息分散存储场景,OP 提供引用以增强安全性和分布式管理能力。


注意事项

  • 如果 OP 不支持聚合声明或分布式声明,而客户端尝试请求这些类型的声明,OP 应仅返回它支持的普通声明值。

5.6.1. 普通声明 (Normal Claims)

定义 普通声明在 JSON 对象中表示为键值对:

  • 声明名称 (Claim Name) 是 JSON 对象的键。
  • 声明值 (Claim Value) 是 JSON 对象中对应键的值。

普通声明由 OpenID 提供者 (OP) 直接生成并返回,不涉及第三方数据源。


示例 以下是一个包含普通声明的非规范性 (non-normative) 响应示例:

{ "sub": "248289761001", "name": "Jane Doe", "given_name": "Jane", "family_name": "Doe", "email": "janedoe@example.com", "picture": "http://example.com/janedoe/me.jpg" }

解释:

  1. "sub": 用户的唯一标识符 (Subject Identifier)。
  2. "name": 用户的完整名称。
  3. "given_name": 用户的名。
  4. "family_name": 用户的姓。
  5. "email": 用户的电子邮件地址。
  6. "picture": 用户的头像 URL。

使用场景 普通声明通常用于以下场景:

  • 身份验证后提供用户信息,例如显示用户名、头像或发送通知的邮箱地址。
  • 返回基本用户信息,满足常见的 Web 应用需求。

特点

  • 简单直接:普通声明由 OP 自行生成,无需外部数据源支持。
  • 广泛支持:普通声明是 OpenID Connect 中的核心功能,所有 OP 都必须实现。

如需更详细的用户信息,可以结合声明参数 (claims) 请求个性化的返回内容,具体见 5.5. 请求声明 部分。

5.6.2. 聚合声明和分布式声明

定义

  • 聚合声明 (Aggregated Claims)分布式声明 (Distributed Claims) 提供了一种机制,让 OpenID 提供者 (OP) 可以通过其他声明提供者 (Claims Providers, CP) 提供声明值。
  • 它们通过 _claim_names_claim_sources 两个特殊成员字段在 JSON 对象中表示。

结构说明

1. _claim_names

  • 是一个 JSON 对象,其中:
    • :声明名称 (Claim Names)。
    • :引用 _claim_sources 中相应声明来源的成员名。

2. _claim_sources

  • 是一个 JSON 对象,其中:
    • :引用名称,与 _claim_names 中的值对应。
    • :描述声明来源,分为以下两种格式:
      1. 聚合声明 (Aggregated Claims):
        • 包含一个必须的 JWT 成员,其值是一个包含所有声明的 JWT。
        • 示例结构
          { "source1": { "JWT": "eyJhbGciOi..." } }
      2. 分布式声明 (Distributed Claims):
        • 包含以下成员:
          • endpoint (必需): 声明的 OAuth 2.0 资源端点 URL。
          • access_token (可选): 一个 Access Token,用于通过 endpoint 获取声明。
        • 示例结构
          { "source2": { "endpoint": "https://example.com/claims", "access_token": "SlAV32hkKG..." } }

使用规则

  1. JWT 的要求 (聚合声明)

    • JWT 必须包含与 _claim_names 引用相对应的所有声明。
    • 如果 JWT 包含 sub (主体声明),其值应该仅作为该声明提供者内的用户标识符。
  2. 端点的要求 (分布式声明)

    • endpoint 必须返回一个包含声明的 JWT。
    • 如果没有 access_token,RP (客户端) 可能需要通过其他方式获取它。
  3. 未返回声明的处理

    • RP 应当能够处理 _claim_sources 中一些声明未返回的情况,将其视为其他请求声明未返回的情况。
  4. 签名验证

    • CP 生成的 JWT 应包含 iss (发行者声明),以便 OP 验证其签名。

示例 以下是包含聚合和分布式声明的非规范性响应:

{ "sub": "248289761001", "name": "Jane Doe", "_claim_names": { "address": "source1", "phone_number": "source2" }, "_claim_sources": { "source1": { "JWT": "eyJhbGciOiJIUzI1Ni..." }, "source2": { "endpoint": "https://example.com/claims", "access_token": "SlAV32hkKG..." } } }

解释

  1. "address" 的值通过 source1 引用到 _claim_sources,其声明值存储在一个 JWT 中。
  2. "phone_number" 的值通过 source2 引用到 _claim_sources,其声明可通过 OAuth 端点和 Access Token 获取。

场景适用性

  • 聚合声明:适用于 OP 直接代理其他声明提供者的声明,减少网络请求。
  • 分布式声明:适用于声明数据量大或需要动态请求的场景,客户端可以通过 endpoint 获取最新声明。

通过这些机制,OpenID Connect 能够支持多来源的声明集成,适用于复杂的身份验证和信息共享场景。

5.6.2.1. 聚合声明示例

在此非规范性示例中,来自声明提供者 A 的声明与 OpenID 提供者持有的其他声明结合,其中来自声明提供者 A 的声明作为聚合声明返回。

示例解释

在这个例子中,关于 Jane Doe 的声明是由声明提供者 A 发行的,并且该示例还包括声明提供者的发行者标识符 URL。

来自声明提供者 A 的声明

{ "iss": "https://a.example.com", "address": { "street_address": "1234 Hollywood Blvd.", "locality": "Los Angeles", "region": "CA", "postal_code": "90210", "country": "United States of America" }, "phone_number": "+1 (310) 123-4567" }

声明提供者 A 签署了这些 JSON 声明,并将它们表示为签名的 JWT(即 jwt_header.jwt_part2.jwt_part3)。这个 JWT 被 OpenID 提供者使用。


聚合声明的结合

这个包含 Jane Doe 的来自声明提供者 A 的聚合声明的 JWT 与 OpenID 提供者的其他常规声明结合,最终返回以下声明集:

{ "sub": "248289761001", "name": "Jane Doe", "given_name": "Jane", "family_name": "Doe", "birthdate": "0000-03-22", "eye_color": "blue", "email": "janedoe@example.com", "_claim_names": { "address": "src1", "phone_number": "src1" }, "_claim_sources": { "src1": { "JWT": "jwt_header.jwt_part2.jwt_part3" } } }

关键点解释

  • subnamegiven_namefamily_namebirthdateeye_coloremail 是常规声明,由 OpenID 提供者直接提供。
  • addressphone_number 是通过聚合声明 (src1 引用) 从声明提供者 A 返回的。
  • _claim_names 列出了聚合声明的名称(如 addressphone_number),并通过 src1 引用 _claim_sources 中的来源。
  • _claim_sources 中包含一个 JWT(jwt_header.jwt_part2.jwt_part3),这是包含 Jane Doe 的地址和电话号码的聚合声明。

总结 这个例子展示了如何将来自多个来源的声明(常规声明和聚合声明)组合在一起。OpenID 提供者将其他来源的声明(如通过外部提供者 A 提供的地址和电话号码)封装在 JWT 中,并将其与 OpenID 提供者自己的声明一起返回。

5.6.2.2. 分布式声明示例

在此非规范性示例中,OpenID 提供者将其持有的常规声明与对两个不同声明提供者 B 和 C 持有的声明的引用结合起来,将 B 和 C 的部分声明作为分布式声明纳入返回。

示例解释

在这个例子中,关于 Jane Doe 的声明由声明提供者 B(Jane Doe 的银行)持有。该示例还包括声明提供者的发行者标识符 URL。

来自声明提供者 B(Jane Doe 的银行)的声明

{ "iss": "https://bank.example.com", "shipping_address": { "street_address": "1234 Hollywood Blvd.", "locality": "Los Angeles", "region": "CA", "postal_code": "90210", "country": "United States of America" }, "payment_info": "Some_Card 1234 5678 9012 3456", "phone_number": "+1 (310) 123-4567" }

同样,关于 Jane Doe 的声明由声明提供者 C(一个信用机构)持有。该示例还包括声明提供者的发行者标识符 URL。

来自声明提供者 C(信用机构)的声明

{ "iss": "https://creditagency.example.com", "credit_score": 650 }

分布式声明的结合

OpenID 提供者将 Jane Doe 的声明与来自声明提供者 B 和 C 的分布式声明的引用结合,返回包含用于检索分布式声明的访问令牌和 URL。

最终返回的声明如下:

{ "sub": "248289761001", "name": "Jane Doe", "given_name": "Jane", "family_name": "Doe", "email": "janedoe@example.com", "birthdate": "0000-03-22", "eye_color": "blue", "_claim_names": { "payment_info": "src1", "shipping_address": "src1", "credit_score": "src2" }, "_claim_sources": { "src1": { "endpoint": "https://bank.example.com/claim_source" }, "src2": { "endpoint": "https://creditagency.example.com/claims_here", "access_token": "ksj3n283dke" } } }

关键点解释

  • subnamegiven_namefamily_nameemailbirthdateeye_color 是常规声明,由 OpenID 提供者直接提供。
  • payment_infoshipping_address 是通过分布式声明 (src1 引用) 从声明提供者 B 返回的。
  • credit_score 是通过分布式声明 (src2 引用) 从声明提供者 C 返回的。
  • _claim_names 列出了分布式声明的名称(如 payment_infoshipping_addresscredit_score),并通过 src1src2 引用 _claim_sources 中的来源。
  • _claim_sources 中包含指向声明提供者的 URL 端点,以及访问令牌(access_token),用于通过 OAuth 2.0 协议检索这些分布式声明。

注意事项

  • 在这个例子中,phone_number 是由声明提供者 B 持有的,但它没有包含在返回的声明中。这表明,并非所有由使用的声明提供者持有的声明都需要包括在返回的响应中。

5.7. 声明的稳定性和唯一性

sub(主体)和 iss(发行者)声明是 RP(依赖方)可以依赖的唯一稳定标识符,作为 End-User(终端用户)的唯一标识符。因为 sub 声明对于某个特定的 End-User 必须在发行者内是本地唯一的,并且永远不会重新分配(如第 2 节所述)。因此,唯一且保证不变的标识符是 isssub 声明的组合。

对于其他声明,除 subiss 外,不能保证其在不同的发行者之间具有稳定性或跨用户的唯一性,且发行者允许应用本地限制和策略。例如,发行者可以在不同时间点重新使用一个邮箱声明值,并且某个 End-User 的声明邮箱地址可能会随时间变化。因此,其他声明,如邮箱(email)、电话(phone_number)、首选用户名(preferred_username)和姓名(name)不得作为 End-User 的唯一标识符, 无论是从 ID Token 还是 UserInfo 端点获取的。

6. 以 JWT 形式传递请求参数

OpenID Connect 定义了以下授权请求参数,用于支持对认证请求进行签名并可选地加密:

  • request (可选)
    该参数允许 OpenID Connect 请求以单个、自包含的参数形式传递,并可选地进行签名和/或加密。参数的值是一个请求对象(Request Object),如第 6.1 节所述。它表示请求作为一个 JWT(JSON Web Token),其中的声明(Claims)是请求的参数。

  • request_uri (可选)
    该参数允许 OpenID Connect 请求通过引用而不是值进行传递。request_uri 的值是一个 URL,指向包含请求对象(Request Object)的资源,该对象是一个包含请求参数的 JWT。此 URL 必须使用 https 协议,除非目标请求对象是以一种可以由身份提供方(OP)验证的方式签名的。

使用这些参数的请求分别以值或引用的形式表示为 JWT。通过引用传递请求的能力对于大型请求特别有用。如果在请求中使用了其中一个参数,则不能同时使用另一个参数。

需要注意的是,此处定义的请求对象与《OAuth 2.0 授权框架:JWT 安全授权请求(JAR)》[RFC9101] 中指定的请求对象兼容。

6.1 按值传递请求对象

request 授权请求参数允许 OpenID Connect 请求通过单个、自包含的参数传递,并可选地进行签名和/或加密。该参数的值是一个 JWT(JSON Web Token),其声明(Claims)为第 3.1.2 节中指定的请求参数。这种 JWT 被称为 请求对象(Request Object)

支持性

  • request 参数的支持是可选的
  • OP(身份提供方)的 request_parameter_supported 探测结果指示 OP 是否支持此参数。
  • 如果 OP 不支持此参数,而 RP(依赖方)使用了它,则 OP 必须返回 request_not_supported 错误。

参数优先级 当使用 request 参数时,JWT 中包含的 OpenID Connect 请求参数值会优先于通过 OAuth 2.0 请求语法传递的值。然而,即使使用了请求对象,也可以通过 OAuth 2.0 请求语法传递参数。这通常用于启用缓存的、预签名(以及可能的预加密)请求对象,其中包含固定的请求参数;而其他可能随每次请求变化的参数(如 statenonce)则作为 OAuth 2.0 参数传递。

必须参数 为了使请求成为有效的 OAuth 2.0 授权请求,response_typeclient_id 参数的值必须使用 OAuth 2.0 请求语法包含,因为它们是 OAuth 2.0 所必需的。这些参数的值必须与请求对象中的值匹配(如果存在)。

即使 scope 参数已包含在请求对象中,仍然必须通过 OAuth 2.0 请求语法传递一个包含 openidscope 参数,以指示底层的 OAuth 2.0 逻辑这是一个 OpenID Connect 请求。

签名和加密

  • 请求对象可以是签名的未签名的(无安全)
    • 如果未签名,在 JOSE 头中使用 none 算法表示。
    • 如果签名,iss(发行方)和 aud(接收方)声明应作为成员包含:
      • iss 的值应为 RP 的 Client ID,除非请求对象由 RP 以外的其他方签名。
      • aud 的值应为或包含 OP 的发行方标识符 URL。
  • 请求对象可以使用 JWE 加密,且可以只加密而不签名。
  • 如果同时执行签名和加密,必须先签名再加密,结果是一个嵌套的 JWT(Nested JWT)。

限制 请求对象中不得包含 requestrequest_uri 参数。


示例 以下是一个非规范的请求对象声明(在 base64url 编码和签名之前):

{ "iss": "s6BhdRkqt3", "aud": "https://server.example.com", "response_type": "code id_token", "client_id": "s6BhdRkqt3", "redirect_uri": "https://client.example.org/cb", "scope": "openid", "state": "af0ifjsldkj", "nonce": "n-0S6_WzA2Mj", "max_age": 86400, "claims": { "userinfo": { "given_name": {"essential": true}, "nickname": null, "email": {"essential": true}, "email_verified": {"essential": true}, "picture": null }, "id_token": { "gender": null, "birthdate": {"essential": true}, "acr": {"values": ["urn:mace:incommon:iap:silver"]} } } }

使用 RS256 算法签名后的请求对象值如下(为了展示方便,有换行):

eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ew0KICJpc3MiOiAiczZCaGRSa3 F0MyIsDQogImF1ZCI6ICJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsDQogInJl c3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsDQogImNsaWVudF9pZCI6ICJzNk JoZFJrcXQzIiwNCiAicmVkaXJlY3RfdXJpIjogImh0dHBzOi8vY2xpZW50LmV4YW1w bGUub3JnL2NiIiwNCiAic2NvcGUiOiAib3BlbmlkIiwNCiAic3RhdGUiOiAiYWYwaW Zqc2xka2oiLA0KICJub25jZSI6ICJuLTBTNl9XekEyTWoiLA0KICJtYXhfYWdlIjog ODY0MDAsDQogImNsYWltcyI6IA0KICB7DQogICAidXNlcmluZm8iOiANCiAgICB7DQ ogICAgICJnaXZlbl9uYW1lIjogeyJlc3NlbnRpYWwiOiB0cnVlfSwNCiAgICAgIm5p Y2tuYW1lIjogbnVsbCwNCiAgICAgImVtYWlsIjogeyJlc3NlbnRpYWwiOiB0cnVlfS wNCiAgICAgImVtYWlsX3ZlcmlmaWVkIjogeyJlc3NlbnRpYWwiOiB0cnVlfSwNCiAg ICAgInBpY3R1cmUiOiBudWxsDQogICAgfSwNCiAgICJpZF90b2tlbiI6IA0KICAgIH sNCiAgICAgImdlbmRlciI6IG51bGwsDQogICAgICJiaXJ0aGRhdGUiOiB7ImVzc2Vu dGlhbCI6IHRydWV9LA0KICAgICAiYWNyIjogeyJ2YWx1ZXMiOiBbInVybjptYWNlOm luY29tbW9uOmlhcDpzaWx2ZXIiXX0NCiAgICB9DQogIH0NCn0.nwwnNsk1-Zkbmnvs F6zTHm8CHERFMGQPhos-EJcaH4Hh-sMgk8ePrGhw_trPYs8KQxsn6R9Emo_wHwajyF KzuMXZFSZ3p6Mb8dkxtVyjoy2GIzvuJT_u7PkY2t8QU9hjBcHs68PkgjDVTrG1uRTx 0GxFbuPbj96tVuj11pTnmFCUR6IEOXKYr7iGOCRB3btfJhM0_AKQUfqKnRlrRscc8K ol-cSLWoYE9l5QqholImzjT_cMnNIznW9E7CDyWXTsO70xnB4SkG6pXfLSjLLlxmPG iyon_-Te111V8uE83IlzCYIb_NMXvtTIVc1jpspnTSD7xMbpL-2QgwUsAlMGzw

公钥 以下是用于验证请求对象签名的 RSA 公钥,格式为 JWK(展示时有换行):

{ "kty": "RSA", "kid": "k2bdc", "n": "y9Lqv4fCp6Ei-u2-ZCKq83YvbFEk6JMs_pSj76eMkddWRuWX2aBKGHAtKlE5P...", "e": "AQAB" }

6.1.1. 使用 “request” 请求参数进行请求**

客户端将授权请求发送到授权端点。

以下是一个使用 request 参数的授权请求的非规范示例(值中换行仅为显示目的):

https://server.example.com/authorize? response_type=code%20id_token &client_id=s6BhdRkqt3 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb &scope=openid &state=af0ifjsldkj &nonce=n-0S6_WzA2Mj &request=eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ew0KICJpc3MiOiA iczZCaGRSa3F0MyIsDQogImF1ZCI6ICJodHRwczovL3NlcnZlci5leGFtcGxlLmN vbSIsDQogInJlc3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsDQogImNsaWV udF9pZCI6ICJzNkJoZFJrcXQzIiwNCiAicmVkaXJlY3RfdXJpIjogImh0dHBzOi8 vY2xpZW50LmV4YW1wbGUub3JnL2NiIiwNCiAic2NvcGUiOiAib3BlbmlkIiwNCiA ic3RhdGUiOiAiYWYwaWZqc2xka2oiLA0KICJub25jZSI6ICJuLTBTNl9XekEyTWo iLA0KICJtYXhfYWdlIjogODY0MDAsDQogImNsYWltcyI6IA0KICB7DQogICAidXN lcmluZm8iOiANCiAgICB7DQogICAgICJnaXZlbl9uYW1lIjogeyJlc3NlbnRpYWw iOiB0cnVlfSwNCiAgICAgIm5pY2tuYW1lIjogbnVsbCwNCiAgICAgImVtYWlsIjo geyJlc3NlbnRpYWwiOiB0cnVlfSwNCiAgICAgImVtYWlsX3ZlcmlmaWVkIjogeyJ lc3NlbnRpYWwiOiB0cnVlfSwNCiAgICAgInBpY3R1cmUiOiBudWxsDQogICAgfSw NCiAgICJpZF90b2tlbiI6IA0KICAgIHsNCiAgICAgImdlbmRlciI6IG51bGwsDQo gICAgICJiaXJ0aGRhdGUiOiB7ImVzc2VudGlhbCI6IHRydWV9LA0KICAgICAiYWN yIjogeyJ2YWx1ZXMiOiBbInVybjptYWNlOmluY29tbW9uOmlhcDpzaWx2ZXIiXX0 NCiAgICB9DQogIH0NCn0.nwwnNsk1-ZkbmnvsF6zTHm8CHERFMGQPhos-EJcaH4H h-sMgk8ePrGhw_trPYs8KQxsn6R9Emo_wHwajyFKzuMXZFSZ3p6Mb8dkxtVyjoy2 GIzvuJT_u7PkY2t8QU9hjBcHs68PkgjDVTrG1uRTx0GxFbuPbj96tVuj11pTnmFC UR6IEOXKYr7iGOCRB3btfJhM0_AKQUfqKnRlrRscc8Kol-cSLWoYE9l5QqholImz jT_cMnNIznW9E7CDyWXTsO70xnB4SkG6pXfLSjLLlxmPGiyon_-Te111V8uE83Il zCYIb_NMXvtTIVc1jpspnTSD7xMbpL-2QgwUsAlMGzw

6.2. 使用引用的方式传递请求对象**

request_uri 授权请求参数允许通过引用而非值的方式传递 OpenID Connect 请求。此参数的使用方式与 request 参数基本相同,不同之处在于请求对象的值是从指定 URL 的资源中获取的,而不是直接传递的。

request_uri_parameter_supported 的发现结果表明 OP(授权服务器)是否支持此参数。如果 OP 不支持此参数,而 RP(客户端)使用了它,则 OP 必须返回 request_uri_not_supported 错误。

当使用 request_uri 参数时,引用的 JWT 中包含的 OpenID Connect 请求参数值将覆盖使用 OAuth 2.0 请求语法传递的参数值。然而,即使使用了 request_uri,仍然可以通过 OAuth 2.0 请求语法传递参数。这通常用于缓存预签名(可能还包括预加密)的请求对象值,其中包含固定的请求参数,同时使用 OAuth 2.0 参数传递会因每次请求而变化的参数,如 statenonce

为了使请求成为有效的 OAuth 2.0 授权请求,必须使用 OAuth 2.0 请求语法包含 response_typeclient_id 参数的值,因为 OAuth 2.0 要求这些参数。如果请求对象中存在这些参数,其值必须与 OAuth 2.0 请求语法中的值一致。

即使引用的请求对象中存在 scope 参数,仍必须使用 OAuth 2.0 请求语法传递包含 openidscope 参数值,以表明这是一个 OpenID Connect 请求。

服务器可以缓存由 request_uri 引用的资源内容。如果引用的资源内容可能发生变化,则 URI 应包括资源内容的 base64url 编码的 SHA-256 哈希值作为 URI 的片段组件。如果 URI 的片段值发生变化,则表明服务器缓存的旧片段值已失效。

需要注意的是,客户端可以通过 OpenID Connect Dynamic Client Registration 1.0 [OpenID.Registration] 规范第 2.1 节定义的 request_uris 参数预先注册 request_uri 值。OP 可以通过 require_request_uri_registration 发现参数要求使用的 request_uri 值必须预先注册。

整个 request_uri 不应超过 512 个 ASCII 字符。

request_uri 引用的资源内容必须是一个请求对象。除非目标请求对象以授权服务器可验证的方式签名,否则 request_uri 值使用的协议必须是 HTTPS。request_uri 值必须可以被授权服务器访问,并且通常也应可以被客户端访问。

以下是一个非规范的示例,展示了可以通过 request_uri 引用的请求对象资源的内容(值中的换行仅用于显示目的):

eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ew0KICJpc3MiOiAiczZCaGRSa3 F0MyIsDQogImF1ZCI6ICJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsDQogInJl c3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsDQogImNsaWVudF9pZCI6ICJzNk JoZFJrcXQzIiwNCiAicmVkaXJlY3RfdXJpIjogImh0dHBzOi8vY2xpZW50LmV4YW1w bGUub3JnL2NiIiwNCiAic2NvcGUiOiAib3BlbmlkIiwNCiAic3RhdGUiOiAiYWYwaW Zqc2xka2oiLA0KICJub25jZSI6ICJuLTBTNl9XekEyTWoiLA0KICJtYXhfYWdlIjog ODY0MDAsDQogImNsYWltcyI6IA0KICB7DQogICAidXNlcmluZm8iOiANCiAgICB7DQ ogICAgICJnaXZlbl9uYW1lIjogeyJlc3NlbnRpYWwiOiB0cnVlfSwNCiAgICAgIm5p Y2tuYW1lIjogbnVsbCwNCiAgICAgImVtYWlsIjogeyJlc3NlbnRpYWwiOiB0cnVlfS wNCiAgICAgImVtYWlsX3ZlcmlmaWVkIjogeyJlc3NlbnRpYWwiOiB0cnVlfSwNCiAg ICAgInBpY3R1cmUiOiBudWxsDQogICAgfSwNCiAgICJpZF90b2tlbiI6IA0KICAgIH sNCiAgICAgImdlbmRlciI6IG51bGwsDQogICAgICJiaXJ0aGRhdGUiOiB7ImVzc2Vu dGlhbCI6IHRydWV9LA0KICAgICAiYWNyIjogeyJ2YWx1ZXMiOiBbInVybjptYWNlOm luY29tbW9uOmlhcDpzaWx2ZXIiXX0NCiAgICB9DQogIH0NCn0.nwwnNsk1-Zkbmnvs ...

6.2.1 请求对象引用的 URI

客户端可以将请求对象(Request Object)资源存储在本地或远程位置,并提供服务器可访问的 URL。这个 URL 就是请求 URI(request_uri)。

关键点

  1. 请求对象的存储位置

    • 客户端可以选择将请求对象存储在一个远程服务器(例如 CDN)或本地服务器,前提是授权服务器能够通过 request_uri 访问到它。
  2. 敏感信息的保护

    • 如果请求对象中包含了需要保护的声明(Claims)值,这些内容不得暴露给除授权服务器外的任何人。
    • 因此,request_uri 必须具有足够的熵(随机性)来确保其唯一性和安全性,避免被恶意猜测和滥用。
  3. 资源的生命周期管理

    • 推荐做法:如果已知某个请求对象不会再被使用,应及时移除对应的资源。或者,在资源的有效期到期后移除,除非已经采取了访问控制措施(例如认证或授权机制)。
  4. 非规范性示例
    一个请求 URI 的示例,展示了 request.jwt 是客户端托管的请求对象:

    https://client.example.org/request.jwt#GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM
    • request.jwt 是实际存储请求对象的位置。
    • #GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM 是增加熵值的片段部分,防止被恶意猜测。

总结
request_uri 是一个引用请求对象资源的 URL,它需要确保敏感信息的安全性,并具有一定的生命周期管理机制。此方式适合在分布式或动态环境下优化授权请求的安全性和效率。

6.2.2 使用 request_uri 请求参数的请求

客户端将授权请求发送到授权端点,同时使用 request_uri 参数来传递请求对象的引用。


示例
以下是一个非规范性示例,展示了如何通过 request_uri 参数发起授权请求(为了便于显示,内容进行了换行):

https://server.example.com/authorize? response_type=code%20id_token &client_id=s6BhdRkqt3 &request_uri=https%3A%2F%2Fclient.example.org%2Frequest.jwt %23GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM &state=af0ifjsldkj &nonce=n-0S6_WzA2Mj &scope=openid

参数解析

  1. response_type=code%20id_token
    表示期望授权服务器返回 授权码code)和 ID Tokenid_token)。

  2. client_id=s6BhdRkqt3
    表示客户端的标识符,用于表明是谁发起了请求。

  3. request_uri=https%3A%2F%2Fclient.example.org%2Frequest.jwt%23GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM

    • 表示请求对象资源的引用地址,经过了 URL 编码。
    • 解码后为:
      https://client.example.org/request.jwt#GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM
    • 授权服务器将通过此 URI 获取完整的请求对象。
  4. state=af0ifjsldkj
    用于防止跨站请求伪造(CSRF)攻击,在请求和响应之间保持状态一致性。

  5. nonce=n-0S6_WzA2Mj
    防重放攻击的随机值。

  6. scope=openid
    表明这是一个 OpenID Connect 请求,客户端期望获取用户身份信息。


工作流程

  1. 客户端构造授权请求,使用 request_uri 参数代替直接在请求中包含大量参数。
  2. 授权服务器根据 request_uri 指向的资源地址,获取并解析请求对象中的具体内容。
  3. 返回响应时,授权服务器会根据请求对象和其他参数返回相应的结果。

优势

  • 安全性提升:敏感数据保存在客户端的安全资源中,减少了直接暴露风险。
  • 带宽优化:通过引用减少了 URL 中的参数长度和复杂性。

6.2.4 使用 request_uri 的理由

使用 request_uri 参数的原因如下:


  1. 解决请求参数过多的问题

    • 当请求参数数量较多时,可能会超出浏览器 URI 的大小限制。
    • 通过引用方式(使用 request_uri)传递请求参数可以解决这一问题。
  2. 降低请求延迟

    • 传递 request_uri(引用请求对象)比直接传递完整的请求值更高效,从而可以减少请求延迟。
  3. 固定参数集合的签名和加密

    • 大多数 RP 的 Claims 请求是固定的。
    • request_uri 允许提前创建一组固定的请求参数,并可对其进行签名和加密。
    • 此时,request_uri 的值相当于一个表示特定固定请求参数集的“工件”(artifact)。
  4. 预注册的好处

    • 在客户端注册(Registration)阶段预先注册一组固定的请求参数,可以让 OP 缓存并预先验证这些请求参数,从而避免在请求阶段重复检索。
  5. 增强内容审核能力

    • 在注册阶段预先注册一组固定的请求参数,OP 可以对这些参数的内容进行审核,从消费者保护和其他角度进行校验。
    • OP 可以自行完成审核,也可以利用第三方服务完成。

小结
使用 request_uri 的主要优点在于优化性能、提升安全性以及增强对请求参数的管理能力,同时可以解决传统方式中的 URI 长度限制问题。

6.3. 验证基于 JWT 的请求

当授权请求中使用 requestrequest_uri 参数时,除了执行第 3.1.2.2、3.2.2.2 或 3.3.2.2 节中指定的验证步骤外,还需要额外进行以下验证:


  1. 验证包含请求对象的 JWT

    • 检查 JWT 的签名是否有效。
    • 验证 JWT 的签发者是否可信,并检查其签发证书或密钥。
    • 如果 JWT 使用加密,确保能够正确解密以提取内容。
  2. 验证请求对象本身

    • 确认 JWT 的内容符合 OpenID Connect 的规范要求。
    • 检查其中的参数是否正确,例如 response_typeclient_id 等是否有效且一致。

通过这些附加步骤,可以确保传递的请求对象(无论是通过值还是通过引用)是可信的且未被篡改,从而提升授权请求的安全性和准确性。

6.3.1. 加密请求对象**

如果授权服务器在其发现文档([OpenID.Discovery])中的 request_object_encryption_alg_values_supportedrequest_object_encryption_enc_values_supported 元素中声明了支持的 JWE 加密算法,或者通过其他方式提供了加密算法,客户端就可以使用这些算法对 JWT(JSON Web Token)进行加密。


授权服务器的要求:

  1. 解密 JWT
    授权服务器必须依据 JSON Web Encryption (JWE ) 规范对加密的 JWT 进行解密。

  2. 处理解密后的请求对象

    • 解密结果可能是签名的请求对象未签名的请求对象(不安全的)。
    • 如果解密后是签名的请求对象,授权服务器必须按照 6.3.2 节 的要求验证签名。
  3. 解密失败的情况
    如果 JWT 的解密失败,授权服务器必须返回一个错误响应。


目的:通过对请求对象进行加密,客户端可以保护敏感数据,避免在传输过程中被泄露或篡改。而授权服务器在解密的同时,可根据需要验证签名,确保数据的完整性和来源的可信性。

6.3.2. 签名的请求对象**

为了执行签名验证,以下步骤必须完成:


签名验证要求:

  1. 匹配签名算法

    • JOSE Header 中的 alg(算法)头部参数必须与客户端注册时设置的 request_object_signing_alg 的值一致,或者与通过其他方式预先注册的值一致。
  2. 验证签名

    • 使用适用于该 client_id 和算法的密钥,对请求对象的签名进行验证。
  3. 失败处理

    • 如果签名验证失败,授权服务器必须返回错误响应。

目的:通过验证签名,授权服务器可以确保请求对象的真实性、完整性和来源的可信性,防止伪造和篡改。

6.3.3. 请求参数的组装与验证**


参数组装

  1. 组装规则
    授权服务器必须根据以下来源组装授权请求参数的集合:

    • Request Object 中的参数值。
    • OAuth 2.0 授权请求参数(去除 requestrequest_uri 参数)。
  2. 参数优先级
    如果某个参数在 Request Object 和 OAuth 授权请求参数中同时存在,则优先使用 Request Object 中的参数值。


请求验证

  • 授权服务器根据组装好的授权请求参数集合,按照使用的流程(例如 Sections 3.1.2.2、3.2.2.2 或 3.3.2.2 中的规范),以正常的方式验证请求。

目的:通过这种方式,授权服务器确保授权请求参数的一致性,并优先采用 Request Object 中的已签名或加密的参数,从而增强安全性和请求的可靠性。

7. 自签发 OpenID 提供者 (Self-Issued OpenID Provider)**


OpenID Connect 支持自签发 OpenID 提供者(Self-Issued OP),即个人的自托管 OP,它们会签发自签名的 ID Token。自签发的 OP 使用特殊的发行者标识符 https://self-issued.me

与其他 OP 通信时使用的消息大多相同。此部分定义了自签发 OP 使用的一些附加参数,以及某些参数值的特定规定。

7.1. 自签发 OpenID 提供者发现**

如果发现过程的输入标识符包含域名 self-issued.me,则不会执行动态发现。相反,将使用以下静态配置值:

{ "authorization_endpoint": "openid:", "issuer": "https://self-issued.me", "scopes_supported": ["openid", "profile", "email", "address", "phone"], "response_types_supported": ["id_token"], "subject_types_supported": ["pairwise"], "id_token_signing_alg_values_supported": ["RS256"], "request_object_signing_alg_values_supported": ["none", "RS256"] }

注意:OpenID 基金会计划托管 OpenID 提供者网站 https://self-issued.me/,包括其 WebFinger 服务,以便对其执行发现时返回上述静态发现信息,使得 RPs 无需特殊处理即可发现自签发 OP。该网站将以实验性方式托管,生产环境的实现不应依赖于该网站,除非 OpenID 基金会承诺以适用于生产环境的方式托管该网站。

7.2. 自签发 OpenID 提供者注册**

在使用自签发 OP 时,注册不是必需的。客户端可以像已经在 OP 上注册并获得以下客户端注册响应一样继续操作:

  • client_id
  • 客户端的 redirect_uri
  • client_secret_expires_at
    0

注意:OpenID 基金会计划托管(无状态)端点 https://self-issued.me/registration/1.0/,该端点返回上述响应,使得 RPs 无需进行特殊处理即可与自签发 OP 完成注册。该网站将以实验性方式托管,生产环境的实现不应依赖于该网站,除非 OpenID 基金会承诺以适用于生产环境的方式托管该网站。

7.2.1. 使用 “registration” 请求参数提供信息**

OpenID Connect 定义了以下授权请求参数,允许客户端向自签发 OpenID 提供者提供额外的注册信息:

  • registration
    可选。此参数由客户端使用,用于向自签发 OP 提供通常在动态客户端注册期间提供给 OP 的信息。其值是一个包含客户端元数据值的 JSON 对象,如 OpenID Connect 动态客户端注册 1.0 规范第 2.1 节所定义。
    如果 OP 不是自签发 OP,则不应使用该参数。

    自签发 OP 不要求这些信息,因此使用此参数是可选的。

registration 参数的值以 UTF-8 编码的 JSON 对象表示(在作为 OAuth 参数传递时最终会被 URL 编码)。当在请求对象中使用时,按照第 6.1 节,JSON 对象作为 registration 成员的值。

通常用于请求自签发 OP 的注册参数包括 policy_uritos_urilogo_uri。如果客户端使用多个重定向 URI,则会使用 redirect_uris 参数来注册它们。最后,如果客户端请求加密响应,通常会使用 jwks_uriid_token_encrypted_response_algid_token_encrypted_response_enc 参数。

7.3. 自签发 OpenID 提供者请求**

自签发 OP 的授权端点是 URI openid:

客户端向授权端点发送认证请求,其中包含以下参数:

必须参数

  • scope
    必需。Scope 参数值,如第 3.1.2 节中所述。

  • response_type
    必需。常量字符串值为 id_token

  • client_id
    必需。客户端的 Client ID 值,在这种情况下,它包含客户端的 redirect_uri 值。
    注意:由于客户端的 redirect_uri URI 值作为 Client ID 进行传递,因此请求中不需要另行包括 redirect_uri 参数。

可选参数

  • id_token_hint
    可选id_token_hint 参数值,如第 3.1.2 节中所述。
    注意:加密内容传递给自签发 OP 是不支持的。

  • claims
    可选claims 参数值,如第 5.5 节中所述。

  • registration
    可选。此参数用于客户端向自签发 OP 提供通常在动态客户端注册期间提供的信息,如第 7.2.1 节中所述。

  • request
    可选。请求对象值,如第 6.1 节中所述。
    注意:加密内容传递给自签发 OP 是不支持的。

其他注意事项

  • 其他参数:其他参数可以被发送。
  • 返回的声明 (Claims):所有声明都会在 ID Token 中返回。
  • URL 限制:整个 URL 的长度不得超过 2048 个 ASCII 字符

非规范性示例
以下是客户端返回的 HTTP 302 重定向响应的非规范性示例。此响应触发用户代理向自签发 OpenID 提供者发送认证请求(为了显示效果,示例中的值进行了换行处理):

HTTP/1.1 302 Found Location: openid://? response_type=id_token &client_id=https%3A%2F%2Fclient.example.org%2Fcb &scope=openid%20profile &state=af0ifjsldkj &nonce=n-0S6_WzA2Mj &registration=%7B%22logo_uri%22%3A%22https%3A%2F%2F client.example.org%2Flogo.png%22%7D

7.4. 自签发 OpenID 提供者响应

OpenID Connect 定义了以下声明 (Claim) 用于自签发 OpenID 提供者的响应:

sub_jwk

  • 必需
    一个公钥,用于验证自签发 OpenID 提供者签发的 ID Token 的签名。该密钥是 JWK 格式的裸密钥 (非 X.509 证书值)。
    • sub_jwk 的值是一个 JSON 对象。
    • 当 OP 不是自签发时,不建议使用 sub_jwk 声明。

自签发 OP 响应的特点

自签发 OpenID 提供者的响应与常规隐式流程 (Implicit Flow) 的响应相同,但具有以下改进:

  1. 响应模式 (Response Mode)

    • 由于是隐式流程的响应,响应参数会返回在 URL 的 片段组件 (fragment) 中,除非指定了不同的响应模式。
  2. iss (issuer) 声明值

    • 值为 https://self-issued.me
  3. sub_jwk 声明

    • 值为用于验证 ID Token 签名的公钥。
  4. sub (subject) 声明值

    • 值为 sub_jwk 声明中的密钥的 拇指指纹 (thumbprint) 的 base64url 编码表示。
    • 拇指指纹计算方式
      • 拇指指纹值是以下内容的 SHA-256 哈希值:

        1. 按照 UTF-8 表示 的 JWK 格式密钥的字节序列。
        2. 构造的 JWK 中仅包含表示密钥所需的必需成员。
        3. 成员名称按照字典顺序排列。
        4. 没有空格或换行符
      • 例如:

        • kty 值为 RSA 时,JWK 中的成员为 e, kty, 和 n,且以此顺序排列。
        • kty 值为 EC 时,JWK 中的成员为 crv, kty, x, 和 y,且以此顺序排列。
      • 此拇指指纹计算方式与 JWK Thumbprint [JWK.Thumbprint] 规范中定义的计算方式相同。

  5. 没有访问令牌 (Access Token)

    • 不会返回用于访问 UserInfo 端点的访问令牌。
    • 因此,所有声明 (Claims) 必须包含在 ID Token 中。

非规范性说明
这确保了自签发的 OpenID 提供者可以通过标准化的方式返回响应,同时保留其独特的安全特性和操作简化策略。

7.5. 自签发 ID Token 验证

为了验证接收到的自签发 ID Token,客户端 (Client) 必须执行以下步骤:


验证步骤

  1. 验证 iss (issuer) 声明的值

    • 客户端必须验证 iss 声明的值是否为 https://self-issued.me
    • 如果 iss 包含其他值,则 ID Token 不是自签发的,应按照 3.1.3.7 节的方式进行验证。
  2. 验证 aud (audience) 声明的值

    • 客户端必须验证 aud 声明是否包含客户端在认证请求中发送的 redirect_uri 的值,作为受众 (audience)。
  3. 验证 ID Token 的签名

    • 根据 JWS  规范,客户端必须使用 JOSE Header 中 alg 参数指定的算法和 sub_jwk 声明中的密钥验证 ID Token 的签名。
    • 使用的密钥是 JWK 格式的裸密钥(而不是 X.509 证书值)。
    • 默认的 alg 值应为 RS256,但也可以是 ES256
  4. 验证 sub (subject) 声明的值

    • 客户端必须验证 sub 声明的值是否是 sub_jwk 声明中密钥的拇指指纹 (thumbprint) 的 base64url 编码表示。拇指指纹的计算方法详见 7.4 节。
  5. 验证 exp (expiration) 声明

    • 当前时间必须小于 exp 声明表示的时间(可以考虑一定的时间偏差,以应对时钟偏移问题)。
  6. 使用 iat (issued at) 声明

    • 客户端可以使用 iat 声明拒绝签发时间距离当前时间过久的 Token,从而限制存储 nonce 的时间范围,以防止攻击。
    • 接受范围是客户端特定的。
  7. 验证 nonce (number used once) 声明

    • 必须存在 nonce 声明,客户端需要检查其值是否与认证请求中发送的值一致。
    • 客户端应检查 nonce 值是否遭受重放攻击。检测重放攻击的具体方法因客户端而异。

非规范性示例

以下是一个 base64url 解码 的自签发 ID Token 的示例:

{ "iss": "https://self-issued.me", "sub": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs", "aud": "https://client.example.org/cb", "nonce": "n-0S6_WzA2Mj", "exp": 1311281970, "iat": 1311280970, "sub_jwk": { "kty": "RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e": "AQAB" } }

说明

  • 以上验证步骤确保了客户端能够正确处理自签发 ID Token,同时检测潜在的安全问题,如签名不匹配或过期 Token。
  • 示例中展示了所有关键字段及其典型用法。

8. Subject Identifier Types

在 OpenID Connect 中,主体标识符 (Subject Identifier) 是一个在同一 Issuer 内本地唯一、且不会被重新分配的标识符,用于标识最终用户 (End-User),并由客户端 (Client) 消费。该规范定义了以下两种主体标识符类型:


主体标识符类型

  1. Public (公共)

    • 所有客户端会收到相同的 sub (subject) 值。
    • 如果提供者的发现文档 (Discovery Document) 中没有 subject_types_supported 元素,此类型为默认类型。
  2. Pairwise (成对)

    • 每个客户端会收到不同的 sub 值,以防止客户端在未经授权的情况下关联最终用户的活动。

发现文档要求

  • OpenID Provider 的发现文档必须在 subject_types_supported 元素中列出其支持的主体标识符类型。
  • 如果数组中列出了多个支持的类型,客户端可以在注册 (Registration) 期间使用 subject_type 参数来指定其首选的标识符类型。

示例说明

发现文档中的 subject_types_supported 示例

{ "subject_types_supported": ["public", "pairwise"] }

此发现文档表示 OP 支持 publicpairwise 两种主体标识符类型。

客户端在注册时指定 subject_type 参数
当客户端偏好使用 pairwise 标识符时,它可以在动态注册 (Dynamic Client Registration) 中指定:

{ "subject_type": "pairwise" }

使用场景

  • Public: 适用于用户无需匿名性、且多客户端可共享用户标识的场景。例如,允许用户跨多个服务进行统一识别的场景。
  • Pairwise: 适用于需要保护用户隐私、避免跨客户端关联用户活动的场景。例如,不同服务提供商间需保持用户活动隔离时。

通过灵活选择标识符类型,可以根据需求平衡隐私保护与统一身份标识的需求。

8.1. Pairwise Identifier Algorithm

当使用成对标识符 (Pairwise Identifiers) 时,OpenID 提供者 (OP) 必须为每个扇区标识符 (Sector Identifier) 计算一个唯一的 sub (subject) 值。计算规则如下:

  1. 不可逆性: 生成的标识符值只能由 OpenID 提供者解析,其他任何方均无法逆向计算得到原始值。
  2. 唯一性: 不同的扇区标识符必须生成不同的主体标识符。
  3. 确定性: 算法必须是确定性的,即相同输入必须生成相同的输出。

扇区标识符 (Sector Identifier)

来源定义

  • 使用 sector_identifier_uri 参数:
    如果在动态客户端注册中提供了 sector_identifier_uri 参数,则其 URL 的 主机部分 (host component) 用作计算扇区标识符。

    • sector_identifier_uri 的值必须是一个使用 HTTPS 的 URL,指向一个 JSON 文件,其中包含一个 redirect_uris 数组。
    • 注册的所有 redirect_uris 必须包含在此数组中。

    示例 JSON 文件:

    [ "https://client.example.com/callback1", "https://client.example.com/callback2" ]
  • 未提供 sector_identifier_uri:

    • 使用注册的 redirect_uri 的主机部分作为扇区标识符。
    • 如果注册的 redirect_uris 包含多个主机名,则客户端必须注册一个 sector_identifier_uri

标识符计算算法

任何满足以下条件的算法均可用于计算成对主体标识符:

  1. 标识符不可逆
  2. 唯一性保障
  3. 算法确定性

示例算法

  1. 基于哈希的算法

    • 将扇区标识符、用户的本地账户 ID 和提供者保密的盐值进行拼接。
    • 使用安全哈希算法对拼接后的字符串进行计算:
      sub = SHA-256( sector_identifier || local_account_id || salt )
  2. 基于加密的算法

    • 将扇区标识符、用户的本地账户 ID 和提供者保密的盐值进行拼接。
    • 使用对称加密算法对拼接后的字符串进行加密:
      sub = AES-128( sector_identifier || local_account_id || salt )
  3. 基于存储的唯一标识符 (GUID)

    • 提供者为每个扇区标识符和本地账户 ID 的组合生成一个全局唯一标识符 (GUID),并存储该值。

优势与应用场景

  • 扇区标识符的灵活性:
    使用 sector_identifier_uri 可以支持多个站点在更改 redirect_uri 时保持一致的标识符值,减少用户重新注册的需求。

  • 隐私保护:
    通过为不同客户端生成唯一的 sub 值,成对标识符有效防止未经授权的关联和跨服务追踪。

  • 确定性与安全性:
    使用不可逆且确定性的算法确保系统安全性和用户隐私。


示例场景

  • 跨域站点:
    某组织有多个子域名(如 example.comsub.example.com),通过注册 sector_identifier_uri 统一标识用户。

  • 用户隐私保护:
    在多个服务提供商之间,确保用户的活动无法被关联。

通过采用适当的算法和 sector_identifier_uri,OpenID 提供者可以在隐私保护和可扩展性之间找到平衡点。

9. Client Authentication

客户端认证定义了一组方法,用于客户端在通过令牌端点 (Token Endpoint) 向授权服务器认证时,验证自身身份。这些方法可在客户端注册时配置。如果未明确注册认证方法,则默认方法为 client_secret_basic


客户端认证方法

  1. client_secret_basic
  • 客户端通过 HTTP Basic 身份验证模式认证。
  • 客户端使用从授权服务器获取的 client_secret,按照 OAuth 2.0 第 2.3.1 节 (RFC6749)  的规范认证。

  1. client_secret_post
  • 客户端通过请求体中的参数认证。
  • 客户端将 client_idclient_secret 直接包含在令牌请求的请求体中,与授权服务器认证,符合 OAuth 2.0 第 2.3.1 节 

  1. client_secret_jwt
  • 客户端使用基于 HMAC 的算法(如 HMAC SHA-256)生成 JWT,用以认证自身身份。
  • 使用 client_secret 作为共享密钥计算 HMAC,生成的 JWT 必须包含以下声明:

必须包含的声明 (Required Claims)

  • iss: 发行者,值为客户端的 client_id
  • sub: 主题,值为客户端的 client_id
  • aud: 受众,值为授权服务器的 Token Endpoint URL,授权服务器会验证此值。
  • jti: JWT ID,令牌的唯一标识符,用于防止重复使用。
  • exp: 过期时间,指定 JWT 的有效期。

可选声明 (Optional Claims)

  • iat: 签发时间,标识 JWT 的生成时间。

  • JWT 可包含其他声明,不被理解的声明必须被忽略。

发送方式

  • 认证令牌作为 client_assertion 参数的值发送。
  • client_assertion_type 参数的值必须为:
    urn:ietf:params:oauth:client-assertion-type:jwt-bearer

  1. private_key_jwt
  • 客户端使用预注册的公钥签名生成 JWT,用以认证自身身份。
  • client_secret_jwt 类似,但使用客户端的私钥签名。

必须包含的声明 (Required Claims)
client_secret_jwt 相同:isssubaudjtiexp

可选声明 (Optional Claims)
client_secret_jwt 相同:iat

发送方式

  • 认证令牌作为 client_assertion 参数的值发送。
  • client_assertion_type 参数的值同样为:
    urn:ietf:params:oauth:client-assertion-type:jwt-bearer

示例请求

POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& code=i1WsRn1uB1& client_id=s6BhdRkqt3& client_assertion_type= urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer& client_assertion=PHNhbWxwOl ... ZT

  1. none
  • 客户端不在 Token Endpoint 认证自身。
  • 通常用于:
    • 仅使用隐式流程 (Implicit Flow),无需令牌端点。
    • 公开客户端 (Public Clients),未注册 client_secret 或其他认证机制。

应用场景与选择方法

  1. 高安全性需求:
    使用 private_key_jwtclient_secret_jwt,确保认证过程的完整性和不可否认性。

  2. 简单场景:
    使用 client_secret_basicclient_secret_post,适用于共享密钥的简单认证流程。

  3. 公开客户端或隐式流程:
    使用 none,适合无需认证的场景。

通过正确选择认证方法,客户端和授权服务器可以实现安全高效的交互。

10. 签名和加密

在消息传输过程中,可能会出现以下风险:

  1. 消息的完整性无法得到保障。
  2. 消息发送方无法被验证身份。

为应对这些风险,可以使用 JSON Web Signature (JWS) 和 JSON Web Encryption (JWE) 对消息进行签名和加密。以下是详细说明:


应用场景

  1. 签名 (Integrity and Authentication)

    • 使用 JWS 对消息签名,确保消息完整性及发送方的真实性。
    • 签名可应用于以下内容:
      • ID Token
      • UserInfo 响应
      • 请求对象 (Request Object)
      • 客户端认证的 JWT 值
  2. 加密 (Confidentiality)

    • 使用 JWE 加密消息内容,以确保消息的保密性。
  3. 同时签名和加密 (Nested JWT)

    • 若需要同时签名和加密:
      • 签名在前,加密在后,即先生成一个签名的 JWT,再对其加密。
      • 结果是一个嵌套的 JWT (Nested JWT),符合 JWT  规范。

消息完整性和密文校验

  • 所有 JWE 加密方法都内置了完整性校验功能,即加密时会自动验证消息的完整性。

开放ID提供方 (OP) 和依赖方 (RP) 的能力声明 1. 算法支持

  • OP (OpenID Provider)
    • 在其 Discovery 文档(或其他方式)中,声明支持的签名和加密算法。
  • RP (Relying Party)
    • 在其 动态注册请求(或其他方式)中,声明所需的签名和加密算法。

2. 公钥声明

  • OP 公钥
    • OP 通过 Discovery 文档(或其他方式)提供其公钥,用于加密或验证签名。
  • RP 公钥
    • RP 在 动态注册请求中,提供其公钥,用于加密响应或验证签名。

注意事项

  1. 签名和加密顺序

    • 签名必须先于加密,确保消息签名可验证,即使加密被破坏,签名仍有效。
  2. 算法选择

    • 签名算法:如 RS256(RSA + SHA-256)、HS256(HMAC + SHA-256)。
    • 加密算法:如 RSA-OAEPAES-128-GCM
  3. 动态注册 (Dynamic Registration)

    • RP 应在注册时明确声明其支持的签名和加密要求,以便 OP 知悉。

通过签名和加密,OpenID Connect 能够确保消息的完整性、认证性和保密性,从而为安全通信提供保障。

10.1 签名(Signing)

签名方必须根据接收方支持的算法选择适当的签名算法。签名用于确保消息的完整性和来源的真实性。

**非对称签名(Asymmetric Signatures)
使用场景:
非对称签名通过私钥进行签名,公钥用于验证,适用于开放环境下的安全通信。

要求

  1. alg 参数
    JOSE Header 的 alg 参数必须设置为符合 JSON Web Algorithms (JWA) 规范的非对称签名算法,例如:

    • RSA 签名(如 RS256
    • ECDSA 签名(如 ES256)。
  2. 密钥管理

    • 签名时,使用发送方的私钥完成。
    • 验证时,接收方通过 JWK Set 文档中发布的公钥验证签名。
  3. 多个密钥时的 kid 参数
    如果 JWK Set 文档中包含多个密钥,JOSE Header 必须提供 kid(Key ID)参数,用于指明签名所用的具体密钥。

  4. 密钥用途
    公钥在 JWK 中的用途声明必须支持签名操作(use: sig)。


**对称签名(Symmetric Signatures)
使用场景:
对称签名适用于客户端与服务器之间的封闭通信环境,要求双方共享一个密钥。

要求

  1. alg 参数
    JOSE Header 的 alg 参数必须设置为符合 JSON Web Algorithms (JWA) 规范的 MAC 算法,例如:

    • HS256(基于 HMAC 的 SHA-256 签名)。
  2. 签名密钥

    • 签名和验证使用客户端的 client_secret 作为密钥。
    • client_secret 是 UTF-8 字符串,其熵必须足够高以确保安全。
  3. 限制

    • 公共客户端(Public Client)由于无法安全存储密钥,禁止使用对称签名。

安全注意事项

  1. 签名算法选择

    • 使用现代、强大的签名算法(如 RS256HS256),避免使用已被破译或不安全的算法。
  2. 密钥保护

    • 私钥和 client_secret 必须妥善保存,防止泄露或未经授权的访问。
  3. 必要性
    签名的请求能有效防止内容被篡改,保护通信的完整性和来源的真实性。

10.1.1 非对称签名密钥的轮换(Rotation of Asymmetric Signing Keys)

密钥轮换方法
非对称签名密钥的轮换可以通过以下方式实现:

  1. JWK Set 的发布
    签名方在 jwks_uri 地址中发布其密钥集合(JWK Set),并在每条消息的 JOSE Header 中通过 kid(Key ID)指明用于验证签名的具体密钥。

  2. 添加新密钥
    签名方可以定期将新的密钥添加到 jwks_uri 地址下的 JWK Set 中,从而实现密钥的轮换。

  3. 新密钥的使用
    签名方可以自主决定何时开始使用新的密钥进行签名,并通过 JOSE Header 中的 kid 值通知验证方。

  4. 验证方的响应
    当验证方遇到一个未识别的 kid 值时,会回到 jwks_uri 重新获取更新后的密钥集合。

  5. 旧密钥的保留

    • 为了确保轮换过程的平滑过渡,jwks_uri 下的 JWK Set 文档应保留最近停用的签名密钥一段合理的时间。

关键点总结

  • 使用 kid 参数确保密钥切换过程的明确性。
  • 定期更新密钥以增强安全性,同时保证系统对旧密钥的兼容性。
  • 验证方需支持从 jwks_uri 动态更新密钥,确保能够处理新签名。

意义
密钥轮换可以有效降低密钥泄露的风险,同时增强系统的安全性和灵活性。

10.2 加密 (Encryption)

加密方必须根据接收方支持的算法选择合适的加密算法。


非对称加密:RSA

  1. 加密所用的公钥
    必须是接收方在其 JWK Set 文档中发布的用于加密的公钥。
    如果 JWK Set 中有多个密钥,必须在 JOSE Header 中提供 kid(Key ID)值以指明具体密钥。

  2. 加密过程
    使用支持的 RSA 加密算法对一个随机的内容加密密钥(Content Encryption Key, CEK)进行加密,并用此密钥加密已签名的 JWT。

  3. 密钥用途要求
    所用密钥的用途必须包括加密功能。


非对称加密:椭圆曲线 (Elliptic Curve)

  1. 临时公钥创建
    为 JOSE Header 的 epk 元素生成一个临时的椭圆曲线(Elliptic Curve)公钥。

  2. 另一方的公钥
    协议计算所用的另一方公钥必须是接收方在其 JWK Set 文档中发布的公钥。
    如果 JWK Set 中有多个密钥,必须在 JOSE Header 中提供 kid(Key ID)值。

  3. 算法与加密密钥
    使用 ECDH-ES 算法协商出一个内容加密密钥(Content Encryption Key, CEK),用于加密已签名的 JWT。

  4. 密钥用途要求
    所用密钥的用途必须支持加密。


对称加密

  1. 密钥生成
    client_secret 值派生对称加密密钥,方法为:

    • 使用 client_secret 的 UTF-8 表示形式的字节流进行 SHA-2 哈希。
    • 根据密钥长度截取哈希值的左侧位数:
      • 256 位及以下使用 SHA-256。
      • 257-384 位使用 SHA-384。
      • 385-512 位使用 SHA-512。
    • 例如:对 SHA-256 哈希值截取 128 位用于 AES-128 加密算法。
  2. 限制与扩展
    如果需要超过 512 位的对称密钥,则必须通过扩展定义新的密钥派生方法。

  3. 使用限制

    • 公共(非机密)客户端不得使用对称加密,因为其无法妥善保管密钥。

安全性考虑 详见 第 16.21 节,讨论加密请求的必要性与安全性。

10.2.1. 非对称加密密钥的轮换 (Rotation of Asymmetric Encryption Keys)

由于加密密钥的轮换过程由加密方发起,因此与签名密钥的轮换过程不同,不能依赖 kid 的更改作为密钥变更的信号。具体说明如下:


加密方的行为

  1. kid 的使用
    加密方在 JWE 的 Header 中仍然使用 kid 参数,告知解密方应使用哪个私钥解密。

  2. 密钥选择
    加密方需要从接收方通过 jwks_uri 提供的 JWK Set 中选择最合适的密钥。


密钥轮换流程

  1. 解密方更新密钥

    • 解密方在其 jwks_uri 位置发布新的密钥。
    • 从 JWK Set 中移除已退役的密钥。
  2. 缓存管理

    • jwks_uri 的响应中应包含 Cache-Control 头,设置 max-age 指令(根据 RFC 7234 [RFC7234]),允许加密方安全地缓存 JWK Set,避免每次加密事件都重新获取文档。
  3. 内部保留已退役密钥

    • 解密方应在 JWK Set 中移除已退役密钥,但内部保留这些密钥一段合理时间。
    • 此时间应与缓存的有效期协调一致,确保加密方有足够时间获取新密钥,平稳完成密钥过渡。

协调与签名密钥同步 密钥缓存的持续时间应与新签名密钥的发布流程(详见第 10.1.1 节)同步协调,以确保加密和签名操作的顺畅衔接。

11. 离线访问 (Offline Access)

OpenID Connect 定义了以下的 scope 值来请求离线访问:


offline_access

  • 可选 (OPTIONAL)
    scope 值请求颁发一个 OAuth 2.0 刷新令牌(Refresh Token),此令牌可用于获取访问令牌(Access Token),即使最终用户(End-User)不在线(未登录),仍然可以访问用户信息端点(UserInfo Endpoint)。

离线访问的处理规则

  1. 需要用户同意 (Consent)
    • 如果请求了离线访问,必须使用 prompt 参数的值为 consent,除非存在其他允许离线访问请求资源的处理条件。
    • OP 必须始终获取用户的同意才能返回允许离线访问所请求资源的刷新令牌。
    • 注意:之前保存的用户同意并不总是足以授予离线访问。

授权服务器的行为 当收到包含 offline_access 值的 scope 参数时,授权服务器必须按照以下规则处理:

  1. 确保 prompt 包含 consent

    • 必须确保 prompt 参数中包含 consent,除非满足其他条件允许离线访问请求资源。
    • 如果以上条件未满足,则必须忽略 offline_access 请求。
  2. 限制响应类型 (response_type)

    • 必须忽略 offline_access 请求,除非客户端使用的 response_type 会返回授权码(Authorization Code)。
  3. Web 应用的要求

    • 对于注册的 application_typeweb 的客户端,必须明确接收或拥有离线访问的同意。
  4. Native 应用的建议

    • 对于注册的 application_typenative 的客户端,建议明确接收或拥有离线访问的同意。

刷新令牌的其他使用场景

  • 刷新令牌的使用并不限于 offline_access 的场景。
  • 授权服务器可以在超出此规范范围的其他上下文中颁发刷新令牌。

12. 使用刷新令牌 (Using Refresh Tokens)

可以通过使用 grant_type 值为 refresh_token 向令牌端点(Token Endpoint)发起请求,这一机制在 OAuth 2.0 的第 6 节中已有描述 [RFC6749]。本节定义了在使用刷新令牌时,OpenID Connect 授权服务器的行为规范。

12.1. 刷新请求 (Refresh Request)

为了刷新访问令牌(Access Token),客户端(Client)必须按照第 9 节中记录的 client_id 注册的认证方法向令牌端点(Token Endpoint)进行认证。客户端通过 HTTP POST 方法向令牌端点发送参数,并使用表单序列化(Form Serialization)方式,如第 13.2 节所述。

以下是一个非规范性的刷新请求示例(为了显示清晰,值中的部分内容换行展示):

POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded client_id=s6BhdRkqt3 &client_secret=some_secret12345 &grant_type=refresh_token &refresh_token=8xLOxBtZp8 &scope=openid%20profile

授权服务器(Authorization Server)必须执行以下验证步骤:

  1. **验证刷新令牌(Refresh Token)**是否合法;
  2. 确认刷新令牌是签发给当前客户端的
  3. 验证客户端已成功完成认证,并检查客户端的认证方法是否符合要求。

12.2. 成功的刷新响应 (Successful Refresh Response)

在成功验证刷新令牌(Refresh Token)后,响应体将是第 3.1.3.3 节中定义的令牌响应(Token Response),但可能不包含 id_token

如果在令牌刷新请求的结果中返回了 ID Token,需满足以下要求:

  1. iss 声明(Claim)值必须与最初认证时签发的 ID Tokeniss 值相同。
  2. sub 声明值必须与最初认证时签发的 ID Tokensub 值相同。
  3. iat 声明值必须表示新 ID Token 签发的时间。
  4. aud 声明值必须与最初认证时签发的 ID Tokenaud 值相同。
  5. 如果 ID Token 包含 auth_time 声明,其值必须表示最初认证的时间,而不是新 ID Token 签发的时间。
  6. 如果实现中使用了超出本规范范围的扩展,并导致 azp(授权方)声明的存在,这些扩展可能要求:
    • azp 的值必须与最初认证时签发的 ID Tokenazp 值相同;
    • 如果最初认证时签发的 ID Token 中没有 azp 声明,新 ID Token不得包含 azp 声明。
  7. 通常情况下,新 ID Token不应包含 nonce 声明,即使最初认证时的 ID Token 包含 nonce;但如果存在,其值必须与最初认证时签发的 ID Token 中的 nonce 值相同。
  8. 其他规则与最初认证时签发 ID Token 的规则一致。

以下是一个非规范性示例的刷新响应:

HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "access_token": "TlBN45jURg", "token_type": "Bearer", "refresh_token": "9yNOxJtZa5", "expires_in": 3600 }

总结: 刷新响应的重点在于:

  • 确保 ID Token(如果返回)与原认证的上下文一致;
  • 响应体包含新的访问令牌和可能的刷新令牌;
  • 遵循 OAuth 2.0 和 OpenID Connect 的相关规则。

12.3. 刷新错误响应 (Refresh Error Response)

如果刷新请求(Refresh Request)无效或未经授权,授权服务器(Authorization Server)会返回符合 [RFC6749] 第 5.2 节定义的令牌错误响应(Token Error Response)。


示例说明

  1. 无效的请求:参数不完整、格式不正确,或请求不符合规范。
  2. 未授权:使用的刷新令牌(Refresh Token)不属于客户端,或者客户端认证失败。

此响应遵循标准 OAuth 2.0 错误格式,包含错误类型和可选的错误描述字段。


参考性错误响应示例

HTTP/1.1 400 Bad Request Content-Type: application/json Cache-Control: no-store { "error": "invalid_request", "error_description": "The refresh token is expired or invalid." }

总结: 刷新错误响应提供了标准化的错误反馈,便于客户端识别问题并采取相应的措施。

13. 序列化 (Serializations)

消息可以使用以下方法之一进行序列化:

  1. 查询字符串序列化 (Query String Serialization)
  2. 表单序列化 (Form Serialization)
  3. JSON 序列化 (JSON Serialization)

说明

  • 本节描述了这些序列化方法的语法规则。
  • 其他章节将详细说明各方法的使用场景以及何时必须使用特定的方法。
  • 请注意,并非所有消息都可以使用所有的序列化方法。

简要解读: 不同的序列化方式适用于不同的传输场景,例如:

  • 查询字符串序列化:用于在 URL 中附加参数。
  • 表单序列化:用于 HTTP POST 请求的表单数据。
  • JSON 序列化:用于更复杂的嵌套结构或需要更多描述的消息格式。

选择适当的序列化方式是确保消息传递和处理正确的关键。

13.1. 查询字符串序列化 (Query String Serialization)

要使用查询字符串序列化参数,客户端需要将参数和值按照 application/x-www-form-urlencoded 格式添加到 URL 的查询组件中。该格式定义于 [W3C.SPSD-html401-20180327] 标准中。查询字符串序列化通常用于 HTTP GET 请求 中,也可以用于向 URL 的片段组件添加参数。


非规范性示例: 以下是这种序列化方法的一个示例(为了显示方便,值内部换行仅用于演示):

GET /authorize? response_type=code &scope=openid &client_id=s6BhdRkqt3 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1 Host: server.example.com

简要说明

  • 参数被添加到 URL 的 查询部分片段部分
  • 参数和值使用 键值对格式,用 = 连接键和值,用 & 分隔多个键值对。
  • 在需要编码的场景中,值需要被 URL 编码(如 https://client.example.org/cb 被编码为 https%3A%2F%2Fclient.example.org%2Fcb)。

典型场景

  • 授权请求:客户端将认证参数嵌入到 URL 查询字符串中,发送到认证服务器。
  • 常用于浏览器重定向或 API GET 请求中。

13.2. 表单序列化 (Form Serialization)

参数及其值通过表单序列化方式被添加到 HTTP 请求的实体部分(Entity Body) 中,采用 application/x-www-form-urlencoded 格式。这一格式定义于 [W3C.SPSD-html401-20180327] 标准中。表单序列化通常用于 HTTP POST 请求


非规范性示例: 以下是表单序列化的一个示例(为了显示方便,值内部换行仅用于演示):

POST /authorize HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded response_type=code &scope=openid &client_id=s6BhdRkqt3 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb

简要说明

  • 参数和值作为键值对通过 = 连接。
  • 多个键值对通过 & 分隔。
  • 值在需要的场景中必须进行 URL 编码,例如:https://client.example.org/cb 被编码为 https%3A%2F%2Fclient.example.org%2Fcb
  • 参数被嵌入到 请求体(Entity Body) 中,而不是 URL 的查询部分。

典型场景

  • 主要用于认证请求中发送敏感信息,例如 OAuth 令牌请求。
  • 由于参数位于请求体中,这种方法相比查询字符串具有更高的安全性,特别是在 HTTPS 连接中。

13.3. JSON 序列化 (JSON Serialization)

参数通过 JSON 序列化为 JSON 对象结构,并将每个参数作为顶层结构的属性添加。以下是具体规则:


序列化规则

  1. 字符串值:参数名和字符串值表示为 JSON 字符串
  2. 数值:数值参数表示为 JSON 数字
  3. 布尔值:布尔参数表示为 JSON 布尔值
  4. 省略规则
    • 无值参数被省略的参数 应当从 JSON 对象中移除。
    • 除非特别规定,不应使用 JSON null 值 表示无值参数。
  5. 复杂值
    • 参数可以是一个 JSON 对象JSON 数组

非规范性示例: 以下是 JSON 序列化的一个示例:

{ "access_token": "SlAV32hkKG", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "8xLOxBtZp8" }

简要说明

  • JSON 格式提供了一种清晰且灵活的方式来表示复杂数据结构。
  • 适合包含嵌套对象或数组值的场景。
  • 常见于返回 OAuth 令牌等响应数据中。

典型场景

  • Token Endpoint 响应:如 OAuth 2.0 或 OpenID Connect 返回令牌时。
  • API 响应:API 返回多种类型的参数值,例如令牌信息、用户信息等。

JSON 序列化比其他方法更适合复杂的、层级化的数据表示,同时能增强人类可读性和机器处理的效率。

14. 字符串操作 (String Operations)

在处理某些 OpenID Connect 消息时,需要将消息中的值与已知值进行比较。例如,UserInfo 端点返回的声明名称可能与特定的声明名称(如 sub)进行比较。然而,比较 Unicode 字符串存在显著的安全问题。

因此,比较 JSON 字符串和其他 Unicode 字符串时,必须遵循以下规则:


比较规则

  1. 移除 JSON 转义字符:需要移除任何 JSON 中应用的转义字符,从而得到 Unicode 代码点的数组。
  2. 禁止 Unicode 规范化
    • 在任何时候,不允许对 JSON 字符串或要与之比较的字符串应用 Unicode 规范化
  3. 比较方法
    • 字符串的比较必须通过 Unicode 代码点到代码点的比较 来执行。

空间分隔的字符串列表

  • 在某些地方,规范使用了由空格分隔的字符串列表。
  • 在所有这些情况下,分隔符必须是 单个 ASCII 空格字符 (0x20)

简要说明

  • Unicode 代码点比较:意味着直接对比字符的编码值,而不是字符本身的显示形式。
  • 转义字符的移除:有助于避免由于转义字符造成的比较误差。
  • 安全性考量:遵循这些规则有助于保证在对比字符串时的安全性和一致性,避免潜在的安全漏洞,例如字符规范化攻击。

这些规则确保了在进行字符串比较时的一致性和可靠性,从而避免了可能的安全漏洞。

15. 实现考虑

本规范定义了依赖方(Relying Parties,RP)和 OpenID 提供方(OpenID Providers,OP)共同使用的功能。预计一些 OpenID 提供方将要求依赖方通过静态的、非联机的配置方式进行配置,而另一些则支持依赖方在没有预先建立关系的情况下动态使用它们。因此,OP 强制实现的功能被分为两组:第一组是适用于所有 OP 的功能,第二组是适用于“动态”OpenID 提供方的功能。 总结: 这段描述阐明了 OpenID Connect 规范在不同类型的 OpenID 提供方中可能有不同的实现要求。一些 OP 需要在 RP 和 OP 之间进行静态配置,即依赖方和提供方需要事先达成配置和认证协议;而其他 OP 则支持动态配置,使得依赖方和提供方在没有事先建立关系的情况下也能互相协作。这种灵活性使得 OpenID Connect 适用于不同的使用场景。

15.1. 所有 OpenID 提供方必须实现的功能

所有 OpenID 提供方(OP)必须实现以下功能,确保符合 OpenID Connect 规范的基本要求:

  1. 使用 RSA SHA-256 签名 ID Token
    OP 必须支持使用 RSA SHA-256 签名 ID Token(RS256)。但如果 OP 仅支持从 Token 端点返回 ID Token,且客户端注册时未指定签名算法,则此要求不适用。

  2. Prompt 参数
    OP 必须支持 prompt 参数,控制用户认证界面的行为,例如强制登录(login)或无提示(none)。

  3. Display 参数
    OP 必须支持 display 参数,用于定义用户界面的展示方式(例如弹窗或嵌入式窗口)。最基本的要求是该参数的使用不应导致错误。

  4. Preferred Locales
    OP 必须支持 ui_localesclaims_locales 参数,用于请求用户界面和返回的 Claims 的首选语言和脚本。最小支持是这些参数的使用不会导致错误。

  5. Authentication Time
    OP 必须支持在请求时,通过 auth_time Claim 返回用户的认证时间。

  6. Maximum Authentication Age
    OP 必须支持通过 max_age 参数来设置最大认证时间,以控制认证有效期。

  7. Authentication Context Class Reference
    OP 必须支持通过 acr_values 参数请求特定的认证上下文类引用值,以进一步细化认证要求。

  • 总结: 这些功能是 OpenID 提供方在实现 OpenID Connect 时必须具备的基础功能。它们确保了认证过程的安全性、灵活性和用户体验的一致性。比如,promptdisplay 参数控制用户认证的提示和界面样式,max_age 限制认证有效期,而 acr_values 则允许客户端要求更严格的认证要求。这些功能使得 OpenID Connect 可以提供强大且安全的身份认证和授权服务。

15.2. 动态 OpenID 提供方必须实现的功能

除前述必须实现的功能外,支持与 Relying Parties (RP) 动态建立关系的 OpenID 提供方(即没有预配置关系的 OP)还必须实现以下功能:

  1. 响应类型(Response Types)
    这些 OpenID 提供方必须支持 id_token 响应类型,且所有非自签发 OP(Self-Issued OP)还必须支持 codeid_token token 响应类型。

  2. 发现(Discovery)
    这些 OpenID 提供方必须支持 OpenID Connect 发现协议,具体参见 OpenID Connect Discovery 1.0 规范。发现机制帮助客户端动态地了解 OP 提供的服务和端点。

  3. 动态注册(Dynamic Registration)
    这些 OpenID 提供方必须支持动态客户端注册,具体参见 OpenID Connect Dynamic Client Registration 1.0 规范。动态注册允许客户端在没有事先配置的情况下注册并获取所需的访问权限。

  4. UserInfo 端点
    所有发放访问令牌的动态 OP 必须支持 UserInfo Endpoint,该端点用于提供与用户相关的额外信息。自签发 OP 不会发放访问令牌,因此不需要支持此端点。

  5. 公开密钥发布为裸 JWK 密钥
    这些 OpenID 提供方必须将其公钥发布为裸 JWK(JSON Web Key)格式,并且可以附带 X.509 格式的密钥表示。这些公钥用于验证签名,以确保消息的完整性和安全性。

  6. 请求 URI(Request URI)
    这些 OpenID 提供方必须支持使用请求对象(Request Object)值的请求,该值可以通过 request_uri 参数从指定的 URI 获取。此功能增强了请求的灵活性和安全性。

总结: 这些功能是动态 OpenID 提供方在与 Relying Party 进行交互时必须实现的基本功能。与传统的静态配置不同,动态 OpenID 提供方允许客户端在没有预先建立关系的情况下动态注册和进行认证,支持更加灵活和自动化的身份验证流程。例如,支持 OpenID 发现和动态注册可以使得客户端无需事先了解 OP 的配置,从而简化了集成过程。而裸 JWK 密钥发布则使得密钥管理更加开放和安全。

15.3. 发现与注册

一些 OpenID Connect 安装可能会使用预配置的 OpenID 提供方和/或依赖方。在这种情况下,可能不需要支持身份或服务信息的动态发现,或者不需要支持客户端的动态注册。

然而,如果系统选择支持依赖方与 OpenID 提供方之间的非预配置关系的交互,它们应该通过实现 OpenID Connect Discovery 1.0 [OpenID.Discovery] 和 OpenID Connect Dynamic Client Registration 1.0 [OpenID.Registration] 规范中定义的功能来实现这一目标。

总结: 这段内容解释了如果系统使用的是预先配置好的 OpenID 提供方和依赖方,则不需要动态发现和注册功能。但如果系统需要支持依赖方与 OpenID 提供方之间没有预配置关系的交互,那么应该支持动态发现和动态客户端注册机制,这样可以使系统更加灵活,适应更多的场景。

15.4. 依赖方必须实现的功能

通常,依赖方在与 OpenID 提供方交互时,可以自行决定使用哪些功能。然而,某些选择受到 OAuth 客户端性质的影响。例如,是否是一个保密客户端(能够保密的客户端),在这种情况下,授权码流程(Authorization Code Flow)可能是适用的;或者是否是一个公共客户端,比如基于用户代理的应用程序或静态注册的本地应用程序,在这种情况下,隐式流程(Implicit Flow)可能更合适。

在使用 OpenID Connect 功能时,那些被列为 “必需” 或以 “MUST” 描述的功能是必须实现的。如果是 “可选” 的功能,除非它们在特定应用场景下提供价值,否则可以不使用或不支持。最后,当与支持发现功能的 OpenID 提供方交互时,RP 可以使用提供方的发现文档动态确定可用的功能。

总结: 依赖方需要根据其客户端的类型(如保密客户端或公共客户端)选择合适的 OpenID Connect 功能。对于必需的功能,依赖方在使用时必须实现,而可选功能则可以根据实际需求来决定是否支持。通过支持动态发现,依赖方可以灵活地根据 OpenID 提供方的能力来选择支持的功能。

15.5. 实现注意事项

15.5.1. 授权码实现注意事项

在使用授权码或混合流时,当客户端通过授权码请求令牌时,授权服务器会从令牌端点返回一个 ID 令牌。此时,一些实现可能会选择将关于要返回的 ID 令牌的状态信息编码到授权码值中,而其他实现可能会使用授权码值作为索引来查找存储此状态的数据库。

关键点:

  1. 授权码的作用:

    • 授权码主要用来将授权的状态从客户端传递到服务器,它是客户端与授权服务器之间交互的中间步骤。
    • 在授权码流程中,授权服务器将颁发一个授权码,该授权码需要由客户端通过令牌请求来交换为访问令牌和 ID 令牌。
  2. ID 令牌的编码与存储:

    • 在某些实现中,授权服务器可能会将 ID 令牌的相关信息(例如,用户身份、会话状态等)编码到授权码值中,允许客户端在接收到授权码后直接从中提取这些信息。
    • 另外,有些实现会将这些状态信息存储在数据库中,并通过授权码值作为索引进行查找。这种方式确保了更灵活的数据管理和状态恢复,避免了在授权码中直接存储敏感信息。
  3. 安全性注意事项:

    • 不论采用哪种方法,开发人员必须确保授权码的传输和存储是安全的。授权码应该只通过安全的方式传输(如 HTTPS)以防止被拦截。
    • 如果使用授权码作为索引访问数据库中的状态数据,确保数据库本身的安全性,避免未授权访问。

总结: 在实现授权码流程时,开发人员需要决定如何处理 ID 令牌的状态信息。无论是将其编码在授权码中,还是通过授权码在数据库中查找,安全性和数据完整性都是必须重点考虑的问题。

15.5.2. Nonce 实现注意事项

nonce 参数的值需要包含每个会话的状态,并且必须对攻击者不可猜测。为了实现这一点,对于 Web 服务器客户端,一种方法是将加密的随机值存储为 HttpOnly 会话 cookie,并使用该值的加密哈希作为 nonce 参数。在这种情况下,返回的 ID 令牌中的 nonce 将与会话 cookie 的哈希值进行比较,以检测是否存在第三方重放 ID 令牌的情况。对于 JavaScript 客户端和其他基于浏览器的客户端,相关的方法是将加密随机值存储在 HTML5 本地存储中,并使用该值的加密哈希。

关键点:

  1. Nonce 的目的:

    • nonce 参数用于防止重放攻击。在每个请求中,nonce 是唯一的,并且每次身份验证时都会生成不同的值,确保每个身份验证请求是独立的。
    • 它的作用是确保攻击者无法重用先前的 ID 令牌来进行恶意操作。
  2. 存储 nonce 的方法:

    • 对于 Web 服务器客户端,可以使用 HttpOnly 会话 cookie 存储加密的随机值。nonce 参数值是该随机值的加密哈希。这样,只有服务器可以访问 cookie,而 JavaScript 无法直接读取 cookie,增加了安全性。
    • 对于 JavaScript 客户端和基于浏览器的客户端,可以使用 HTML5 本地存储 来存储加密随机值。然后,客户端会将该值的哈希作为 nonce 传递。该方法适用于需要在浏览器中处理身份验证请求的客户端应用。
  3. 防止重放攻击:

    • 使用加密随机值及其哈希来确保第三方不能通过重放之前的请求获取有效的 ID 令牌。服务器可以在接收到 ID 令牌时验证其 nonce 是否与存储的值的哈希匹配,从而确保请求的唯一性。

安全性注意事项:

  • 不可猜测性: 确保 nonce 的值足够随机且不可预测,这样可以有效防止攻击者猜测值并重用旧的令牌。
  • 加密存储: 确保存储在会话 cookie 或本地存储中的 nonce 值加密并且不可被客户端轻易篡改,防止恶意用户伪造有效的 nonce 参数。

总结: nonce 是防止身份验证令牌重放攻击的重要机制。通过将其存储在安全的地方(如 HttpOnly cookie 或 HTML5 本地存储),并确保其值不可猜测且不可被篡改,可以增强身份验证流程的安全性。

15.5.3. 重定向 URI Fragment 处理实现注意事项

当响应参数通过重定向 URI 的片段值(fragment)返回时,客户端需要让用户代理(User Agent)解析片段编码的值,并将其传递给客户端的处理逻辑。用户代理如果能够直接访问加密 API,可能能够自行处理这些值,比如在 JavaScript 中编写的客户端代码。

然而,如果客户端并不完全在用户代理中运行(例如,客户端涉及 Web 服务器处理),则可以通过将数据通过 HTTP POST 发送到 Web 服务器进行验证和处理来实现。

关键点:

  1. 解析 Fragment 并传递给服务器:

    • 通过重定向 URI 传递的响应参数通常作为 URI 的片段(#)部分返回。客户端需要解析该片段部分,并将解析的参数发送到 Web 服务器以进行进一步的处理和验证。
  2. JavaScript 客户端示例:

    • 以下示例展示了一个可能的 JavaScript 文件。客户端通过该文件在重定向后处理 URI 片段。脚本解析 URI 中的片段部分,并将数据通过 POST 请求发送到服务器,服务器验证并处理数据。
    <script type="text/javascript"> // 首先,解析查询字符串 var params = {}, postBody = location.hash.substring(1), regex = /([^&=]+)=([^&]*)/g, m; while (m = regex.exec(postBody)) { params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); } // 然后将令牌发送给服务器 var req = new XMLHttpRequest(); // 使用 POST 请求,避免查询参数被记录 req.open('POST', 'https://' + window.location.host + '/catch_response', true); req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); req.onreadystatechange = function (e) { if (req.readyState == 4) { if (req.status == 200) { // 如果 POST 响应为 200 OK,则执行重定向 window.location = 'https://' + window.location.host + '/redirect_after_login'; } else if (req.status == 400) { // 如果 OAuth 响应无效,生成错误信息 alert('处理令牌时发生错误'); } else { alert('返回的状态码不是 200'); } } }; req.send(postBody); </script>
  3. POST 请求以防止日志记录:

    • 使用 POST 请求而不是 GET 请求来发送数据,这样可以避免 URL 参数在日志中被记录,确保令牌和敏感数据不被泄露。
  4. 验证并重定向:

    • 客户端通过发送 POST 请求将解析后的数据提交到 Web 服务器进行验证。服务器收到验证结果后,如果验证成功,则重定向用户到登录后的页面。

安全性注意事项:

  • 数据保护: 使用 POST 请求避免 URL 中包含敏感数据,减少日志泄露的风险。
  • 服务器验证: Web 服务器负责验证重定向 URI 片段中的参数,确保 OAuth 响应的有效性和安全性。
  • 避免重定向攻击: 服务器应确保接受到的重定向 URI 是合法的,并且所有请求都经过适当的身份验证和授权。

总结: 当 OAuth 响应通过重定向 URI 的片段部分返回时,客户端需要能够解析片段中的参数并将其安全地发送到 Web 服务器进行处理。使用 POST 请求可以减少日志泄露的风险,而服务器则负责验证响应的有效性。

15.6. 兼容性说明

注意: 之前在本规范的原始版本中描述的潜在兼容性问题,已经在后续的版本中得到了处理。

解释: 这一部分指出,原始版本的 OpenID Connect 规范中提到了一些可能导致兼容性问题的内容,但这些问题已经在后续版本中得到解决。用户和开发者不需要担心这些兼容性问题,因为最新的版本已经进行修正或改进,确保了更好的兼容性和一致性。

总结: 本节提醒用户之前提到的兼容性问题已被解决,因此在实现时可以参考当前版本,不必考虑这些已被修复的历史问题。

15.7. 相关规范和实现者指南

以下是与本规范相关的可选规范,这些规范可以与本规范结合使用,以提供额外的功能:

  1. OpenID Connect Discovery 1.0 [OpenID.Discovery] - 定义了如何动态发现 OpenID 提供者的信息。
  2. OpenID Connect Dynamic Client Registration 1.0 [OpenID.Registration] - 定义了如何动态注册 Relying Party(RP)与 OpenID 提供者。
  3. OAuth 2.0 Form Post Response Mode [OAuth.Post] - 定义了如何使用 HTML 表单值返回 OAuth 2.0 授权响应参数(包括 OpenID Connect 身份验证响应参数),这些表单值通过 HTTP POST 自动提交给用户代理。
  4. OpenID Connect RP-Initiated Logout 1.0 [OpenID.RPInitiated] - 定义了如何由 Relying Party 请求 OpenID 提供者登出最终用户。
  5. OpenID Connect Session Management 1.0 [OpenID.Session] - 定义了如何管理 OpenID Connect 会话,包括基于 postMessage 的登出和 RP 发起的登出功能。
  6. OpenID Connect Front-Channel Logout 1.0 [OpenID.FrontChannel] - 定义了一种前端通道登出机制,不使用 OpenID 提供者 iframe。
  7. OpenID Connect Back-Channel Logout 1.0 [OpenID.BackChannel] - 定义了一种通过 OP 与 RP 之间的直接后端通道通信来执行登出的机制。

此外,以下是面向Web 基础 Relying Parties(基本 Web 客户端)的实现者指南,旨在为实现基本功能提供自包含的参考:

  1. OpenID Connect Basic Client Implementer’s Guide 1.0 [OpenID.Basic] - 面向使用 OAuth 授权代码流的基本 Web Relying Parties 的实现者指南。
  2. OpenID Connect Implicit Client Implementer’s Guide 1.0 [OpenID.Implicit] - 面向使用 OAuth 隐式流的基本 Web Relying Parties 的实现者指南。

解释: 这些规范和指南为开发人员提供了更多的参考和实现细节,能够帮助在实际应用中实现 OpenID Connect 协议的功能,涵盖了从基本的客户端实现到复杂的会话管理和登出机制等多个方面。

16. 安全性考虑

本规范参考了以下几个文献中的安全性考虑:

  1. OAuth 2.0 [RFC6749] 的第 10 节;
  2. OAuth 2.0 Bearer Token Usage [RFC6750] 的第 5 节;
  3. OAuth 2.0 威胁模型和安全性考虑 [RFC6819] 提供了一个详细的威胁和控制列表,这些威胁和控制同样适用于本规范,因为它是基于 OAuth 2.0 的。
  4. ISO/IEC 29115 [ISO29115] 也提供了实现者需要考虑的威胁和控制。

建议实现者详细阅读这些参考文献,并采取其中描述的对策。

额外的攻击向量和应对措施: 在此之外,规范还列出了一些攻击向量和应对措施,供实现者参考。

解释:

  • 本节强调了安全性的重要性,特别是对 OpenID Connect 和 OAuth 2.0 协议实施的安全性。实现者在部署 OpenID Connect 时,必须考虑多种安全威胁,并且应根据相关标准采取适当的防护措施。
  • 参考文献提供了深入的技术细节,帮助实施者理解和避免潜在的安全风险。

16.1. 请求泄露

如果没有采取适当的措施,请求可能会被攻击者泄露,从而带来安全和隐私威胁。

除了 [RFC6819] 第 5.1.1 节中提到的内容之外,本标准提供了一种方式,通过使用 requestrequest_uri 参数,确保请求的端到端机密性,其中请求内容为使用适当的密钥和加密算法加密的 JWT(JSON Web Token)。即使在用户代理被攻破的情况下,这种方式仍能保护请求内容,尤其在间接请求的情况下也能有效防护。

解释:

  • 本节强调了请求泄露的风险,特别是在没有适当保护的情况下,可能导致敏感信息泄漏给攻击者。
  • 为了防止泄露,推荐通过加密的 JWT 来确保请求内容的机密性,即使在攻击者可能控制用户代理的情况下,数据仍然是安全的。

16.2. 服务器伪装

恶意服务器可能通过各种手段伪装成合法服务器。为了检测这种攻击,客户端需要对服务器进行身份验证。

除了 [RFC6819] 第 5.1.2 节中提到的内容外,本标准提供了一种通过使用签名或加密的 JWT(JSON Web Token)以及适当的密钥和加密算法来对服务器进行身份验证的方法。

解释:

  • 伪装攻击是一种恶意服务器冒充合法服务器的攻击方式,通常通过伪造服务器的身份来获取敏感信息。
  • 本标准建议使用签名或加密的 JWT 来验证服务器的身份,确保客户端与合法的服务器进行通信。这有助于防止伪装服务器的攻击。

16.3. 令牌伪造/修改

攻击者可能会生成虚假的令牌,或修改现有可解析令牌的内容(如声明值或签名),导致 Relying Party (RP) 错误地授予客户端不应有的访问权限。例如,攻击者可能修改令牌以延长有效期;客户端可能修改令牌以访问他们本不应查看的信息。

有两种方法可以减轻此攻击:

  1. 令牌可以由 OP(OpenID 提供者)进行数字签名。Relying Party 应该验证数字签名,以确保令牌是由合法的 OP 发出的。
  2. 令牌可以通过保护的通道(如 TLS)发送。有关使用 TLS 的详细信息,请参阅第 16.17 节。在本规范中,令牌总是通过 TLS 保护的通道发送。然而,请注意,这种措施仅对第三方攻击者有效,对于客户端本身是攻击者的情况,不能提供保护。

解释:

  • 令牌伪造/修改 是一种攻击方式,攻击者修改令牌内容(如延长有效期或修改敏感信息),以获得不应授予的权限。
  • 为了防止这种攻击,建议:
    1. 对令牌进行数字签名,以便客户端能够验证令牌的来源。
    2. 使用安全的传输协议(如 TLS)来保护令牌的传输,防止中间人攻击。

16.4. 访问令牌泄露

访问令牌是用于访问受保护资源的凭证,如 OAuth 2.0 第 1.4 节所定义。访问令牌代表终端用户的授权,绝不能暴露给未授权的方

解释:

  • 访问令牌 是用于授权客户端访问用户数据的凭证。如果未经授权的方获得了访问令牌,可能会导致用户数据泄露或被滥用。
  • 为了保护访问令牌,必须采取措施防止其泄露,特别是在网络传输过程中使用加密(如 TLS)。

16.5. 服务器响应泄露

服务器响应可能包含认证数据和敏感的客户端信息。若响应内容被泄露,可能使客户端容易受到其他类型的攻击。

防护措施:

  1. 使用 code 响应类型:响应通过 TLS 保护的通道发送,其中客户端通过 client_idclient_secret 进行认证。
  2. 对于其他响应类型:可以使用客户端的公钥加密响应,或者使用共享密钥进行加密,将响应内容包装成加密的 JWT,并使用合适的密钥和加密算法保护。

解释:

  • 服务器响应泄露可能暴露认证信息和敏感数据,进而让攻击者利用这些信息发起攻击。
  • 使用加密(例如 TLS 和加密的 JWT)来保护响应数据,是防止响应泄露的有效措施。

16.6. 服务器响应否认

如果没有采取适当的机制,服务器可能会否认其响应。例如,如果服务器没有对响应进行数字签名,服务器可以声称该响应并非通过其服务生成。

防护措施:

  • 响应可以选择由服务器使用支持不可否认性的密钥进行数字签名。
  • 客户端应该验证数字签名,确保响应是由合法的服务器签发,并且响应的完整性没有受到损害。

解释:

  • 服务器可能会否认某些响应,这可能会引发安全问题,尤其是在认证和授权过程中。
  • 通过使用数字签名,客户端可以验证响应的来源和完整性,确保它没有被篡改,防止服务器事后否认其响应。

16.7. 请求否认

由于可能存在被攻击或恶意的客户端向错误方发送请求的情况,因此仅使用承载令牌进行认证的客户端可以否认任何交易。

防护措施:

  • 服务器可以要求客户端使用支持不可否认性的密钥对请求进行数字签名。
  • 服务器应该验证数字签名,以确保请求是由合法的客户端签发的,并且请求的完整性没有受到损害。

解释:

  • 如果客户端通过承载令牌进行认证,可能会发生客户端否认发送的请求,尤其是在恶意客户端的情况下。
  • 通过要求客户端对请求进行数字签名,服务器可以验证请求的来源,确保请求未被篡改,防止客户端事后否认其行为。

16.8. 访问令牌重定向

攻击者使用为一个资源生成的访问令牌来访问第二个资源。

防护措施:

  • 访问令牌应该受到受众(audience)和作用域(scope)的限制。
  • 一种实现方式是将生成令牌时的资源标识符作为访问令牌的受众。资源方可以验证传入的令牌是否包含其标识符作为令牌的受众。

解释:

  • 如果访问令牌没有明确限制其受众或作用域,攻击者可能会通过访问令牌来绕过授权,获取未授权的资源。
  • 通过将令牌的受众限制为特定资源,只有目标资源可以接受该令牌,防止令牌被重定向或滥用。

16.9. 令牌重用

攻击者尝试使用一个已经被使用过的令牌(如授权码),再次访问目标资源。

防护措施:

  • 令牌应该包含时间戳和短期有效期。
  • 依赖方(Relying Party)检查时间戳和有效期值,以确保令牌仍然有效。

或者,服务器可以记录令牌的使用状态,并在每次请求时检查该状态。

解释:

  • 为防止令牌被重用,可以在令牌中添加时间戳和短有效期,确保它仅在有限的时间窗口内有效。
  • 另外,记录令牌使用状态也能有效防止重用攻击,通过对令牌的每次请求进行检查来保证其唯一性和时效性。

16.10. 监听或泄露授权码(次级身份验证捕获)

除了 [RFC6819] 第4.4.1.1节中描述的攻击模式之外,授权码可能在终止TLS会话的用户代理中被捕获,如果用户代理被恶意软件感染。然而,只要使用客户端身份验证或加密响应,捕获授权码将没有用处。

防护措施:

  • 客户端身份验证:确保只有合法的客户端可以使用授权码。
  • 加密响应:确保响应在传输过程中是加密的,即使授权码被捕获,恶意攻击者也无法解密和利用它。

解释:

  • 授权码的捕获风险存在于用户代理(如浏览器)被恶意软件感染的情况下。通过实施客户端身份验证或加密响应,即使攻击者捕获了授权码,仍然无法进行有效的攻击。

16.11. 令牌替换

令牌替换是一类攻击,其中恶意用户交换各种令牌,包括将授权码与攻击者拥有的另一个令牌进行交换。一种实现此攻击的方法是攻击者将一个会话中的令牌复制出来,并在另一个会话的HTTP消息中使用它,这在令牌可以被浏览器访问时非常容易实现,这被称为“剪切和粘贴”攻击。

OAuth 2.0 的隐式授权流 [RFC6749] 并未设计来缓解这种风险。在第10.16节中,它规范性地要求,任何用作委托终端用户身份验证形式的授权过程,不得在没有附加安全机制的情况下使用隐式流,以确保客户端可以确定ID令牌和访问令牌是为其使用而颁发的。

在OpenID Connect中,这通过ID令牌提供的机制来缓解。ID令牌是一个签名的安全令牌,提供了诸如 iss(颁发者)、sub(主题)、aud(受众)、at_hash(访问令牌哈希)、c_hash(授权码哈希)等声明。通过ID令牌,客户端能够检测到令牌替换攻击。

  • c_hash:ID令牌中的 c_hash 可防止授权码替换。
  • at_hash:ID令牌中的 at_hash 可防止访问令牌替换。

此外,恶意用户可能通过破坏授权端点与客户端之间或令牌端点与客户端之间的通信渠道,试图冒充更高权限的用户,例如通过交换授权码或重新排序消息,使令牌端点认为攻击者的授权授权与更高权限用户的授权相匹配。

对于本规范定义的HTTP绑定,令牌请求的响应通过HTTP中的消息顺序与相应的请求绑定,因为响应包含令牌且请求均通过TLS保护,这会检测并防止数据包重新排序。

当设计将本规范绑定到无法强绑定令牌端点请求和响应的协议时,必须使用附加机制来解决此问题。一种这样的机制是在令牌请求和响应中包含带有 c_hash 声明的ID令牌。

16.12. 时间攻击**

描述:
时间攻击是一种通过观察成功和失败的解密操作或签名验证操作的执行时间差异,推测出大量敏感信息的攻击方式。这种攻击可能被用来降低加密算法的有效密钥长度,从而危害系统的安全性。

防护措施:
实现时,不应在发现错误后立即终止验证过程,而是应继续运行直到所有字节都被处理完毕。这种方式可以防止攻击者通过时间差来推测信息。

简单解释:
时间攻击是利用操作耗时的微小差异来推测加密信息的一种攻击手段。为防止攻击,验证过程应该在每次操作中耗时一致,即便发现错误也应完成整个过程。

16.13. 其他加密相关攻击**

描述:
根据使用的加密和签名/完整性校验方法,可能会面临多种加密相关攻击。具体的攻击类型取决于所采用的加密机制和实现细节。

防护措施:
实现者需要参考 [JWT] 规范的安全注意事项,以及其引用的相关规范,以避免这些规范中提到的安全漏洞。

简单解释:
加密和签名方法可能存在安全风险,开发者应仔细阅读 JWT 和相关规范的安全指导,避免已知的漏洞问题。

16.14. 签名和加密的顺序**

描述:
在许多司法管辖区内,对加密后的文本进行签名并不被认为是有效的。因此,为了确保数据的完整性和不可抵赖性,本规范要求对明文的 JSON 声明进行签名。如果需要同时进行签名和加密,则应对包含签名声明的 JWS 进行加密,最终形成一个嵌套的 JWT(Nested JWT),具体见 [JWT] 规范。此外,由于所有的 JWE 加密算法都提供完整性保护,因此无需对加密内容单独进行签名。

简单解释:
加密和签名时,顺序很重要。需要先对明文数据签名,然后再加密已签名的数据,这样可以保证合法性和安全性。如果只加密,JWE 本身已经提供完整性保护,无需再额外签名。

16.15. Issuer 标识符**

描述:
OpenID Connect 支持每个主机和端口组合下的多个 Issuer(颁发者)。通过发现机制返回的 issuer 值必须与 ID Token 中的 iss 值完全匹配。

OpenID Connect 将 Issuer URI 的路径部分视为 Issuer 标识符的一部分。例如,Issuer 标识符为 https://example.com 的主体 1234 与 Issuer 标识符为 https://example.com/sales 的主体 1234 是不同的。

建议每个主机仅使用一个 Issuer。如果主机支持多租户,则可能需要为该主机配置多个 Issuer。

简单解释:

  • issuer 是 OpenID Connect 中的重要标识,用来区分不同的身份提供者。
  • 必须确保 issuer 和 ID Token 中的 iss 完全匹配。
  • 不同路径的 issuer 是独立的,例如 https://example.comhttps://example.com/sales 是两个不同的 Issuer。
  • 尽量一个主机只使用一个 Issuer,但多租户环境可能需要多个 Issuer。

16.16. Implicit Flow 威胁**

描述:
在 Implicit Flow(隐式流程)中,访问令牌(Access Token)通过 HTTPS 在客户端的 redirect_uri 的片段组件中返回。因此,该令牌在以下场景中受到保护:

  1. 在身份提供者(OP)和用户代理(User Agent)之间。
  2. 在用户代理(User Agent)和依赖方(RP)之间。

唯一可能捕获令牌的地方是用户代理(User Agent),因为这是 TLS 会话终止的地方。如果用户代理感染了恶意软件或被恶意方控制,令牌可能会被泄露。

简单解释:

  • Implicit Flow 会直接将访问令牌返回到用户代理的 URL 中。
  • 通过 HTTPS 保护,令牌通常在传输过程中是安全的。
  • 如果用户代理被恶意软件感染或被攻击者控制,令牌可能会被拦截。

关键点:

  • Implicit Flow 易受用户代理安全问题的影响,例如恶意软件或浏览器扩展的攻击。
  • 强调用户代理安全的重要性,例如使用受信任的设备和浏览器来防止令牌泄露。

16.17. TLS 要求**

描述:
实现必须支持 TLS。支持的具体版本会随着时间推移而变化,并依赖于实现时的广泛部署情况和已知的安全漏洞。实现应遵循 BCP 195([RFC8996] 和 [RFC9325])中的指导,这些文档提供了关于使用 TLS 的服务安全改进的建议和要求。

为了防止信息泄露和篡改,必须使用提供机密性和完整性保护的 TLS 加密套件来应用机密性保护。

每当使用 TLS 时,必须根据 RFC 6125 [RFC6125] 进行 TLS 服务器证书检查。


简单总结:

  • 必须支持 TLS: 系统需要支持安全传输协议 TLS,以保护数据传输安全。
  • 参考标准: 建议遵循 BCP 195 提供的安全性改进建议。
  • 加密要求: 需要使用既能提供机密性又能保证完整性的加密套件。
  • 证书校验: 在使用 TLS 时,必须校验服务器证书,确保通信双方的身份合法可信。

关键点: TLS 是安全通信的基础,必须正确实现并遵循最新的安全标准,以减少潜在漏洞。

16.18. 访问令牌和刷新令牌的生命周期**

描述:

  • 访问令牌可能无法由授权服务器撤销,因此应将访问令牌的生命周期设置为单次使用或非常短的时间。
  • 如果需要持续访问用户信息端点或其他受保护资源,则可以使用刷新令牌。客户端可以使用刷新令牌在令牌端点交换新的短期有效的访问令牌,以便继续访问资源。
  • 授权服务器应在授权期间明确识别长期授权,并应提供机制让终端用户撤销授予客户端的访问令牌和刷新令牌。

简单总结:

  • 访问令牌生命周期: 访问令牌应为单次使用或短期有效,避免长期有效令牌可能带来的安全风险。
  • 使用刷新令牌: 通过使用刷新令牌,客户端可以在令牌过期后继续访问资源。
  • 撤销机制: 授权服务器应提供用户撤销访问令牌和刷新令牌的机制,增强安全性。

关键点: 确保访问令牌的生命周期适当,并提供撤销机制以增强安全性。

16.19. 对称密钥熵**

描述:

  • 在第10.1节和第10.2节中,密钥是由client_secret值派生的。因此,当与对称签名或加密操作一起使用时,client_secret值必须包含足够的熵,以生成加密强度的密钥。
  • 此外,client_secret值必须至少包含针对特定算法所需的最小字节数。例如,对于HS256算法,client_secret值必须至少包含32个字节(并且最好包含更多,因为client_secret值可能会使用受限的字母表)。

简单总结:

  • 熵要求: client_secret值必须具有足够的熵,确保生成的对称密钥具有足够的安全性。
  • 字节要求: 对于HS256等算法,client_secret值需要至少32个字节,确保密钥强度。

关键点: 确保client_secret值具有足够的熵和字节数,以防止生成弱密钥,提升安全性。

16.20. 需要签名请求**

描述:
在某些情况下,客户端可能需要使用签名请求,以确保所需的请求参数在传递给授权服务器(OP)时没有被篡改。例如,max_ageacr_values可以在签名请求中提供更多关于执行的身份验证的保证。


简单总结:

  • 签名请求的必要性: 在某些情况下,客户端需要通过签名请求来确保请求参数的完整性和防篡改。
  • 增强信任: 使用签名请求能够提供更多关于身份验证过程的保证,如max_age(最大认证时长)和acr_values(认证上下文类值)。

关键点: 签名请求可以增强安全性,确保请求的真实性和未被篡改。

16.21. 需要加密请求**

描述:
在某些情况下,了解OpenID Connect请求的内容本身可能会暴露有关终端用户的敏感信息。例如,知道客户端请求了特定的声明(Claim)或要求使用特定的认证方法可能会泄露终端用户的敏感信息。OpenID Connect允许对请求进行加密,以防止此类敏感信息的泄露。


简单总结:

  • 加密请求的必要性: 在某些情况下,客户端的请求内容可能暴露终端用户的敏感信息。为了保护这些信息不被泄露,OpenID Connect支持加密请求。
  • 敏感信息防泄露: 加密请求能有效防止请求中的敏感数据被第三方获取。

关键点: 通过加密请求,可以防止终端用户的敏感信息在传输过程中被暴露。

16.22. HTTP 307 重定向**

描述:
HTTP 307重定向会将一个POST请求发送到被重定向的方,并包含来自先前请求的所有表单数据。这可能会泄露本应只提供给OpenID提供者的凭证给依赖方(Relying Party)。因此,HTTP 307重定向在重定向到重定向URI时必须避免使用。同样,虽然HTTP 302重定向通常以不会泄露凭证的方式实现,但推荐使用HTTP 303重定向,因为它的定义确保不会发生此类泄露。


简单总结:

  • HTTP 307重定向的风险: 它可能泄露凭证给依赖方,因此在OpenID Connect协议中应避免使用HTTP 307重定向。
  • 推荐使用HTTP 303: 与HTTP 307相比,HTTP 303的定义确保不会泄露敏感信息。

关键点: 使用HTTP 307重定向可能导致凭证泄露,应避免使用,推荐使用HTTP 303重定向。

16.23. iOS 上的自定义 URI 方案**

描述:
在 iOS 系统中,多个应用可以注册为自定义 URI 方案的处理程序,因此无法确定调用的应用程序是否会从自我签发的 OpenID 提供者收到认证回复。使用声明的 URI 是一种替代方案,不使用 openid: 自定义 URI 方案。

虽然可以为自定义 URI 方案分配处理程序,并且操作系统可能帮助最终用户选择正确的处理程序,但无法保证给定自定义 URI 方案的处理程序未被后续安装的本地应用程序替换。截至目前,似乎没有万无一失的方式来缓解这个漏洞。


简单总结:

  • iOS上的URI方案问题: iOS允许多个应用处理同一自定义URI方案,这可能导致身份验证信息被错误的应用程序接收。
  • 建议: 使用声明的URI作为替代方案,并注意自定义URI方案的处理程序可能被后续安装的应用程序替换。

关键点: 在iOS上,无法保证特定的应用会正确处理自定义URI方案,建议使用声明的URI以减少安全风险。