在 Azure Active Directory B2C 中使用 OpenID Connect 进行 Web 登录

OpenID Connect 是构建在 OAuth 2.0 基础之上的身份验证协议,可用于将用户安全登录到 Web 应用程序。 通过使用 OpenID Connect 的 Azure Active Directory B2C (Azure AD B2C) 实现,可以将 Web 应用程序中的注册、登录和其他标识管理体验转移到 Microsoft Entra 中。 本指南演示如何使用与语言无关的方式执行此操作。 介绍在不使用我们的任何开放源代码库的情况下,如何发送和接收 HTTP 消息。

注意

大多数开源身份验证库会为应用程序获取并验证 JWT 令牌。 我们建议浏览这些选项,而不是实现自己的代码。 有关详细信息,请参阅 Microsoft 身份验证库 (MSAL) 概述Microsoft 标识 Web 身份验证库

OpenID Connect 扩展了 OAuth 2.0 授权协议,将其用作身份验证协议。 使用此身份验证协议可以执行单一登录。 它引入了 ID 令牌的概念,可让客户端验证用户的标识,并获取有关用户的基本配置文件信息。

OpenID Connect 还能使应用程序安全地获取访问令牌。 可以使用访问令牌访问授权服务器保护的资源。 如果要构建的 Web 应用程序托管在服务器中并通过浏览器访问,我们建议使用 OpenID Connect。 有关令牌的详细信息,请参阅 Azure Active Directory B2C 中的令牌概述

Azure AD B2C 扩展了标准 OpenID Connect 协议,使其功能远远超出了简单的身份验证和授权。 它引入了用户流参数,可让你使用 OpenID Connect 向应用程序添加注册、登录和配置文件管理等用户体验。

发送身份验证请求

当 Web 应用程序需要对用户进行身份验证并运行用户流时,它可以将用户定向到 /authorize 终结点。 用户可以根据用户流执行操作。

在此请求中,客户端指示需要在 scope 参数中从用户获取的权限,并指定要运行的用户流。 若要大致了解请求的工作方式,请将该请求粘贴到浏览器中并运行它。 将:

  • {tenant} 替换为租户的名称。
  • 00001111-aaaa-2222-bbbb-3333cccc4444 替换为在租户中注册的应用程序的应用 ID。
  • {application-id-uri}/{scope-name} 替换为你在租户中注册的应用程序的应用程序 ID URI 和范围。
  • {policy} 替换为你在租户中具有的策略名称,例如 b2c_1_sign_in
GET /{tenant}.partner.onmschina.cn/{policy}/oauth2/v2.0/authorize?
Host: {tenant}.b2clogin.cn

client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&response_type=code+id_token
&redirect_uri=https%3A%2F%2Fjwt.ms%2F
&response_mode=fragment
&scope=openid%20offline_access%20{application-id-uri}/{scope-name}
&state=arbitrary_data_you_can_receive_in_the_response
&nonce=12345
参数 必需 说明
{tenant} Azure AD B2C 租户的名称。
{policy} 应用运行的用户流或策略。 指定在 Azure AD B2C 租户中创建的用户流的名称。 例如:b2c_1_sign_inb2c_1_sign_upb2c_1_edit_profile
client_id Azure 门户分配给应用程序的应用程序 ID。
nonce 建议 由应用程序生成且包含在请求中的值,以声明方式包含在生成的 ID 令牌中。 应用程序接着便可确认此值,以减少令牌重新执行攻击。 此值通常是随机的唯一字符串,可用以识别请求的来源。
response_type 必须包含 OpenID Connect 的 ID 令牌。 如果 Web 应用程序还需要使用令牌来调用 Web API,则可以使用 code+id_token
scope 范围的空格分隔列表。 openid 作用域表示允许使用 ID 令牌的形式使用户登录并获取有关用户的数据。 offline_access 范围对 Web 应用程序是可选的。 它表示应用程序需要使用刷新令牌来长期访问资源https://{tenant-name}/{app-id-uri}/{scope} 指示对受保护资源(例如 Web API)的权限。 有关详细信息,请参阅请求访问令牌
prompt 需要的用户交互类型。 此时唯一有效的值为 login,这会强制用户在该请求上输入其凭据。
redirect_uri 应用程序的 redirect_uri 参数,服务器通过该参数向应用程序发送身份验证响应。 它必须完全匹配在 Azure 门户中注册的其中一个 redirect_uri 参数,但必须经过 URL 编码。
response_mode 将生成的授权代码发回到应用程序所用的方法。 这可以是 queryform_postfragment。 建议使用 form_post 响应模式获得最佳安全性。
State 一个值,你可在请求中包含该值,授权服务器会在令牌响应中返回该值。 可以是所需的任何内容的字符串。 随机生成的唯一值通常用于防止跨站点请求伪造攻击。 该状态也用于在身份验证请求出现之前,在应用程序中编码用户的状态信息,例如用户之前所在的页面。 如果你不想在 Azure 门户中注册多个重定向 URL,可以使用 state 参数,以将应用程序中的响应与 Azure AD B2C 服务中的响应区分开(由于请求不同)。
login_hint 可用于预先填充登录页面的“登录名”字段。 有关详细信息,请参阅预填充登录名
domain_hint 向 Azure AD B2C 提供有关应该用于登录的社交标识提供者的提示。 如果包含了有效的值,用户将直接转到标识提供者登录页面。 有关详细信息,请参阅将登录重定向到社交服务提供商
自定义参数 可用于自定义策略的自定义参数。 例如,动态自定义页面内容 URI键值声明解析程序

此时,要求用户完成工作流。 用户可能需要输入其用户名和密码、用社交标识登录,或注册目录。 可能还可任何其他若干步骤,具体取决于如何定义用户流。

用户完成用户流后,会使用 response_mode 参数中指定的方法,将响应返回到 redirect_uri 参数中指定的应用程序。 对于上述每种情况,响应均相同,而与用户流无关。

使用 response_mode=fragment 的成功的响应如下所示:

GET https://jwt.ms/#
id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...
&code=AwABAAAAvPM1KaPlrEqdFSBzjqfTGBCmLdgfSTLEMPGYuNHSUYBrq...
&state=arbitrary_data_you_can_receive_in_the_response
参数 说明
id_token 应用程序请求的 ID 令牌。 可以使用 ID 令牌验证用户的标识,开始与用户建立会话。
code 如果使用了 response_type=code+id_token,则为应用程序请求的授权代码。 应用程序可以使用该授权代码请求目标资源的访问令牌。 授权代码通常在约 10 分钟后即会过期。
state 如果请求中包含 state 参数,响应中就应该出现相同的值。 应用程序需验证请求和响应中的 state 值是否相同。

错误响应也可能会被发送到 redirect_uri 参数,以便应用程序对它们进行恰当的处理:

GET https://jwt.ms/#
error=access_denied
&error_description=AADB2C90091%3a+The+user+has+cancelled+entering+self-asserted+information.%0d%0aCorrelation+ID%3a+xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx%0d%0aTimestamp%3a+xxxx-xx-xx+xx%3a23%3a27Z%0d%0a
&state=arbitrary_data_you_can_receive_in_the_response
参数 说明
error 一个代码,可用于对发生的错误类型进行分类。
error_description 帮助识别身份验证错误根本原因的特定错误消息。
state 如果请求中包含 state 参数,响应中就应该出现相同的值。 应用程序需验证请求和响应中的 state 值是否相同。

验证 ID 令牌

仅收到一个 ID 令牌并不表示可以对用户进行身份验证。 根据应用程序的要求验证 ID 令牌的签名和令牌中的声明。 Azure AD B2C 使用 JSON Web 令牌 (JWT) 和公钥加密对令牌进行签名并验证其是否有效。

注意

大多数开源身份验证库会为应用程序验证 JWT 令牌。 我们建议浏览这些选项,而不是实现自己的验证逻辑。 有关详细信息,请参阅 Microsoft 身份验证库 (MSAL) 概述Microsoft 标识 Web 身份验证库

Azure AD B2C 具有 OpenID Connect 元数据终结点,允许应用程序在运行时获取有关 Azure AD B2C 的信息。 此信息包括终结点、令牌内容和令牌签名密钥。 B2C 租户中的每个用户流都有一个 JSON 元数据文档。 例如,fabrikamb2c.partner.onmschina.cnb2c_1_sign_in 用户流的元数据文档位于:

https://fabrikamb2c.b2clogin.cn/fabrikamb2c.partner.onmschina.cn/b2c_1_sign_in/v2.0/.well-known/openid-configuration

此配置文档的一个属性为 jwks_uri,对于相同用户流,该属性的值为:

https://fabrikamb2c.b2clogin.cn/fabrikamb2c.partner.onmschina.cn/b2c_1_sign_in/discovery/v2.0/keys

若要确定对 ID 令牌签名所用的用户流,可以采取两种做法。 首先,用户流名称包含在 ID 令牌的 acr 声明中,请参阅表示用户流的声明。 另一个方法是在发出请求时在 state 参数的值中对用户流进行编码,然后对其进行解码以确定使用的用户流。 任意一种方法均有效。

从 OpenID Connect 元数据终结点获取元数据文档后,可以使用 RSA-256 公钥来验证 ID 令牌的签名。 此终结点上可能列出多个密钥,每个密钥使用 kid 声明进行标识。 ID 令牌的标头还包含 kid 声明,该声明指示哪个密钥用于对 ID 令牌进行签名。

若要从 Azure AD B2C 验证令牌,需要使用指数 (e) 和模数 (n) 生成公钥。 为此,需要了解如何使用所选编程语言生成公钥。 可在以下网页中找到有关使用 RSA 协议生成公钥的官方文档: https://tools.ietf.org/html/rfc3447#section-3.1

验证了 ID 令牌的签名后,还有各种声明需要验证。 例如:

  • 验证 nonce 声明以防止令牌重放攻击。 其值应为在登录请求中指定的内容。
  • 验证 aud 声明以确保已为应用程序颁发 ID 令牌。 其值应为应用程序的 ID。
  • 验证 iatexp 声明以确保 ID 令牌未过期。

此外,还需要执行更多的一些验证。 OpenID Connect 核心规范中详细介绍了验证。根据情况,可能还希望验证其他声明。 一些常见的验证包括:

  • 确保用户/组织已注册该应用程序。
  • 确保用户拥有正确的授权/权限。
  • 确保通过某种方式执行了一定强度的身份验证,例如,通过 Microsoft Entra 多重身份验证。

验证 ID 令牌后,便可以开始与用户的会话。 在应用程序中,可以使用 ID 令牌中的声明来获取用户的相关信息。 此信息的用途包括显示、记录和授权。

获取令牌

如果仅需要 Web 应用程序运行用户流,则可以跳过下面几个部分。 这些部分只适用于需要对某个 Web API(它受 Azure AD B2C 本身的保护)发出经过身份验证的调用的 Web 应用程序。

通过将 POST 请求发送到 /token 终结点,可以将获取的授权代码(通过 response_type=code+id_token 获取)兑换为所需资源的令牌。 在 Azure AD B2C 中,可以像往常一样通过在请求中指定其他 API 的范围来为这些 API 请求访问令牌

还可以为应用自己的后端 Web API 请求访问令牌。 在这种情况下,将使用应用的客户端 ID 作为请求的范围,这会导致具有该客户端 ID 的访问令牌作为“受众”:

POST https://{tenant}.b2clogin.cn/{tenant}.partner.onmschina.cn/{policy}/oauth2/v2.0/token HTTP/1.1
Host: {tenant}.b2clogin.cn
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&scope=00001111-aaaa-2222-bbbb-3333cccc4444 offline_access
&code=AwABAAAAvPM1KaPlrEqdFSBzjqfTGBCmLdgfSTLEMPGYuNHSUYBrq...
&redirect_uri=urn:ietf:wg:oauth:2.0:oob
参数 必需 说明
{tenant} Azure AD B2C 租户的名称
{policy} 用于获取授权代码的用户流。 无法在此请求中使用不同的用户流。 将此参数添加到查询字符串中,而不是添加到 POST 正文中。
client_id Azure 门户分配给应用程序的应用程序 ID。
client_secret 是,在 Web 应用中 Azure 门户中生成的应用程序机密。 客户端密码在此流中用于 Web 应用场景,在其中客户端可以安全地存储客户端密码。 对于本机应用(公共客户端)场景,客户端密码不能安全地存储,因此不能在此流上使用。 如果使用客户端密码,请定期更改。
code 在用户流的开头获取的授权代码。
grant_type 授予类型,该类型必须是授权代码流的 authorization_code
redirect_uri 在其中收到授权代码的应用程序的 redirect_uri 参数。
scope 范围的空格分隔列表。 openid 作用域表示允许使用 id_token 参数的形式使用户登录并获取有关用户的数据。 它可以用于为应用程序的后端 Web API 获取令牌,该令牌使用和客户端相同的应用程序 ID 表示。 offline_access 范围表示应用程序需要使用刷新令牌来长期访问资源。

成功的令牌响应如下所示:

{
    "not_before": "1442340812",
    "token_type": "Bearer",
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...",
    "scope": "00001111-aaaa-2222-bbbb-3333cccc4444 offline_access",
    "expires_in": "3600",
    "expires_on": "1644254945",
    "refresh_token": "AAQfQmvuDy8WtUv-sd0TBwWVQs1rC-Lfxa_NDkLqpg50Cxp5Dxj0VPF1mx2Z...",
}
参数 说明
not_before 令牌生效的纪元时间。
token_type 令牌类型值。 Bearer 是唯一支持的类型。
access_token 请求的已签名的 JWT 令牌。
scope 令牌的有效范围。
expires_in 访问令牌有效的时间长度(以秒为单位)。
expires_on 访问令牌失效的纪元时间。
refresh_token OAuth 2.0 刷新令牌。 应用程序可以使用此令牌,在当前令牌过期之后获取其他令牌。 刷新令牌可用于延长保留资源访问权限的时间。 必须在授权和令牌请求中使用范围 offline_access,才能接收刷新令牌。

错误响应如下所示:

{
    "error": "invalid_grant",
    "error_description": "AADB2C90080: The provided grant has expired. Please re-authenticate and try again. Current time: xxxxxxxxxx, Grant issued time: xxxxxxxxxx, Grant expiration time: xxxxxxxxxx\r\nCorrelation ID: xxxxxxxx-xxxx-xxxX-xxxx-xxxxxxxxxxxx\r\nTimestamp: xxxx-xx-16 xx:10:52Z\r\n"
}
参数 说明
error 一个代码,可用于对发生的错误类型进行分类。
error_description 帮助识别身份验证错误根本原因的消息。

使用令牌

你成功获取访问令牌后,可通过在 Authorization 标头中加入令牌的方式,在后端 Web API 请求中使用该令牌:

GET /tasks
Host: mytaskwebapi.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...

刷新令牌

访问令牌和 ID 令牌的生存期较短。 过期后,必须将其刷新才能继续访问资源。 在刷新访问令牌时,Azure AD B2C 会返回新令牌。 刷新的访问令牌将更新 nbf(不早于)、iat(颁发时)和 exp(过期)声明值。 所有其他声明值都类似于上一个访问令牌中的声明值。

通过向 /token 终结点提交另一个 POST 请求来刷新令牌。 此时,提供 refresh_token 参数而不是 code 参数:

POST https://{tenant}.b2clogin.cn/{tenant}.partner.onmschina.cn/{policy}/oauth2/v2.0/token HTTP/1.1
Host: {tenant}.b2clogin.cn
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&scope=openid offline_access
&refresh_token=AwABAAAAvPM1KaPlrEqdFSBzjqfTGBCmLdgfSTLEMPGYuNHSUYBrq...
&redirect_uri=urn:ietf:wg:oauth:2.0:oob
参数 必需 说明
{tenant} Azure AD B2C 租户的名称
{policy} 用于获取原始刷新令牌的用户流。 无法在此请求中使用不同的用户流。 将此参数添加到查询字符串中,而不是添加到 POST 正文中。
client_id Azure 门户分配给应用程序的应用程序 ID。
client_secret 是,在 Web 应用中 Azure 门户中生成的应用程序机密。 客户端密码在此流中用于 Web 应用场景,在其中客户端可以安全地存储客户端密码。 对于本机应用(公共客户端)场景,客户端密码不能安全地存储,因此不能用于此调用。 如果使用客户端密码,请定期更改。
grant_type 授予类型,该类型必须是这一部分授权代码流的 refresh_token
refresh_token 在流的第二部分获取的原始刷新令牌。 必须在授权和令牌请求中使用范围 offline_access,才能接收刷新令牌。
redirect_uri 在其中收到授权代码的应用程序的 redirect_uri 参数。
scope 范围的空格分隔列表。 openid 作用域表示允许使用 ID 令牌的形式使用户登录并获取有关用户的数据。 它可以用于向应用程序的后端 Web API 发送令牌,该令牌使用和客户端相同的应用程序 ID 表示。 offline_access 范围表示应用程序需要使用刷新令牌来长期访问资源。

成功的令牌响应如下所示:

{
    "not_before": "1442340812",
    "token_type": "Bearer",
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...",
    "scope": "00001111-aaaa-2222-bbbb-3333cccc4444 offline_access",
    "expires_in": "3600",
    "refresh_token": "AAQfQmvuDy8WtUv-sd0TBwWVQs1rC-Lfxa_NDkLqpg50Cxp5Dxj0VPF1mx2Z...",
    "refresh_token_expires_in": "1209600"
}
参数 说明
not_before 令牌生效的纪元时间。
token_type 令牌类型值。 Bearer 是唯一支持的类型。
access_token 请求的已签名 JWT 令牌。
作用域 令牌的有效范围。
expires_in 访问令牌有效的时间长度(以秒为单位)。
refresh_token OAuth 2.0 刷新令牌。 应用程序可以使用此令牌,在当前令牌过期之后获取其他令牌。 刷新令牌可用于延长保留资源访问权限的时间。
refresh_token_expires_in 刷新令牌有效的时间长度(以秒为单位)。

错误响应如下所示:

{
    "error": "invalid_grant",
    "error_description": "AADB2C90129: The provided grant has been revoked. Please reauthenticate and try again.\r\nCorrelation ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\nTimestamp: xxxx-xx-xx xx:xx:xxZ\r\n",
}
参数 说明
error 一个代码,可用于对发生的错误类型进行分类。
error_description 帮助识别身份验证错误根本原因的消息。

发送注销请求

如果想要从应用程序中注销用户,只是清除应用程序的 Cookie 或者结束与用户的会话是不够的。 需将用户重定向到 Azure AD B2C 进行注销。如果没有这么做,那么用户可能可以在应用程序中重新进行身份验证,且无需再次输入其凭据。 有关详细信息,请参阅 Azure AD B2C 会话行为

若要将用户注销,请将用户重定向到前面所述的 OpenID Connect 元数据文档中列出的 end_session_endpoint

GET https://{tenant}.b2clogin.cn/{tenant}.partner.onmschina.cn/{policy}/oauth2/v2.0/logout?post_logout_redirect_uri=https%3A%2F%2Fjwt.ms%2F
参数 必需 说明
{tenant} Azure AD B2C 租户的名称
{policy} 在授权请求中指定的用户流。 例如,如果用户已使用 b2c_1_sign_in 用户流登录,请在注销请求中指定 b2c_1_sign_in
id_token_hint 以前颁发的 ID 令牌,该令牌将作为有关最终用户当前与客户端建立的身份验证会话的提示传递给注销终结点。 id_token_hint 确保 post_logout_redirect_uri 是 Azure AD B2C 应用程序设置中的已注册回复 URL。 有关详细信息,请参阅保护注销重定向
client_id 否* Azure 门户分配给应用程序的应用程序 ID。

*使用 Application 隔离 SSO 配置并且注销请求中的所需 ID 令牌设置为 No 时,这是必需的。
post_logout_redirect_uri 用户在成功注销后应重定向到的 URL。如果未包含此参数,Azure AD B2C 会向用户显示一条常规消息。 除非提供 id_token_hint,否则不应在 Azure AD B2C 应用程序设置中将此 URL 注册为回复 URL。
state 如果在授权请求中包含 state 参数,则授权服务器在响应中将同一值返回给 post_logout_redirect_uri。 应用程序需验证请求和响应中的 state 值是否相同。

收到注销请求后,Azure AD B2C 会使 Azure AD B2C 基于 Cookie 的会话失效,并尝试从联合标识提供者处注销。 有关详细信息,请参阅单一注销

保护注销重定向

注销后,用户将重定向到 post_logout_redirect_uri 参数中指定的 URI,而不管为应用程序指定的回复 URL 为何。 但是,如果传递了有效的 id_token_hint 并启用了“注销请求中需要 ID 令牌”,则在执行重定向之前,Azure AD B2C 将验证 post_logout_redirect_uri 的值是否与应用程序的某个已配置重定向 URI 相匹配。 如果没有为应用程序配置匹配的回复 URL,则会显示一条错误消息,而用户不会重定向。

若要在注销请求中设置所需的 ID 令牌,请参阅在 Azure Active Directory B2C 中配置会话行为

后续步骤