实现递增身份验证不需要让应用程序编排对多个复杂 API 的调用。 相反,通过利用开放标准中已有的功能,您可以使用所有应用程序最有可能已经在使用的协议库为所有应用程序构建低摩擦、无状态的递增身份验证。
在本文中,您将了解什么是递增身份验证,以及如何使用 OAuth 在 API 中使用递增身份验证,并使用 OpenID Connect 在客户端应用程序中以无状态方式使用递增身份验证。 我什至会抛出一个使用 SAML 2.0 的奖励示例。
您还将看到一种新兴方法,其中 API(受保护的资源)本身可以触发递增身份验证。
什么是递增身份验证?
一个很好的例子是,当用户使用其用户名和密码进行身份验证,但现在他们即将执行高风险操作时。 例如,他们可能正在进行高额付款或访问应用程序的管理区域。 此时,您希望挑战他们以证明自己的身份到更高的水平,可能使用不同的因素,例如 TOTP 或生物识别技术。 此提升过程是递增身份验证。
递增身份验证对于保护高价值操作非常有用,但它也可以改善用户体验。 例如,某些多重身份验证 (MFA) 方法的一个问题是它们会增加用户身份验证旅程的摩擦。 通过使用递增身份验证,您可以允许用户使用低摩擦身份验证方法获取初始访问权限,仅在必要时使用 MFA。 话虽如此,仅凭密码不足以保护您的用户帐户,因此不要以此为借口。
在开放标准和基于声明的身份中加强身份验证
用于递增身份验证的构建块在开放标准中可用,例如 OAuth 和 OpenID Connect;您只需要知道如何使用它们。 这些允许您为 API 访问 (OAuth) 和应用程序访问 (OpenID Connect) 实施递增身份验证。
这两种方法都使用由授权服务器颁发并由令牌接收者(API 或 OAuth 客户端应用程序)验证的标准化声明。 这些声明可以嵌入到标识令牌中,也可以通过内省终结点或令牌本身使用访问令牌进行检索。
这些声明包括:
acr
– 身份验证上下文参考 (ACR) 描述了进行身份验证的级别。 ACR 值是双方提前商定的。 例如,英国的开放式银行使用 ACR 值 来声明用户按照强客户身份验证 (SCA) 标准进行身份验证。urn:openbanking:psd2:sca
amr
– 身份验证方法参考 (AMR) 描述了用户如何进行身份验证。 例如,“pwd”表示密码,“otp”表示一次性密码,甚至“mfa”表示多重身份验证 (MFA) 已发生。 有关示例值,请参阅 RFC 8176。auth_time
– 最终用户上次在身份提供程序进行身份验证的时间。 如果他们有一个长期的单一登录会话,这可能是几个小时前。
-
使用 OpenID Connect 对应用程序访问进行递增身份验证
使用 OpenID Connect,您可以使用身份令牌来了解用户如何在身份提供程序处对自己进行身份验证。 此令牌允许应用程序决定是否允许用户在应用中启动会话。 例如,应用可能会查看用户上次进行身份验证的时间(他们可能正在使用长期存在的 SSO 会话)以及他们进行身份验证的方式(他们使用的方法或级别)。
许多应用程序仅检查 ID 令牌是否有效,并因此创建自己的会话。 但使用身份令牌,您可以开始使用 OpenID Connect 实现应用程序级递增身份验证。
初始 OpenID 连接授权请求
客户端应用程序要求标识提供者访问用户的标识并提供标识令牌。
HTTP/1.1 302 Found
Location: https://idp.example.com/authorize?
response_type=code
&scope=openid profile email
&client_id=s6BhdRkqt3
&redirect_uri=https://client.example.org/cb
用户将对自己进行身份验证或使用其现有的 SSO 会话并同意共享其身份。 来回一会儿后,客户端应用程序将收到一个标识令牌。 标识令牌的此有效负载将包含有关用户如何进行身份验证的一些基本声明:
{
"iss": "https://idp.example.com",
"aud": "s6BhdRkqt3",
"sub": "5be86359073c434bad2da3932222dabe",
"auth_time": "1645783823",
"amr": [ "pwd" ],
"exp": 1645784123,
"iat": 1645783823
}
此时,这可能足以在客户端应用程序中启动会话。 例如,Web 应用程序将发出自己的 cookie。
使用 OpenID Connect 启动递增身份验证
但是,在访问客户端应用程序的高风险区域时,当前会话可能不够好,并且应用程序将希望更好地保证它是合法用户。 因此,它将需要用户使用递增身份验证向更高级别进行身份验证。
它可以改为要求标识提供者对用户进行身份验证到某个级别,而不是实现身份验证方法本身。 它可以在授权请求中使用来执行此操作。
HTTP/1.1 302 Found
Location: https://idp.example.com/authorize?
response_type=code
&scope=openid profile email
&client_id=s6BhdRkqt3
&redirect_uri=https://client.example.org/cb
&acr_values=http://schemas.openid.net/pape/policies/2007/06/multi-factor
此 ACR(身份验证上下文引用)值告知标识提供者将用户身份验证到特定级别。 例如,上述请求使用值 ,告知身份提供程序使用 MFA。 因此,如果用户的当前会话仅使用单因素(即密码)进行身份验证,则标识提供者将提示用户使用其第二个因素进行身份验证。http://schemas.openid.net/pape/policies/2007/06/multi-factor
用户重新进行身份验证以满足新的身份验证级别后,新的标识令牌将返回到客户端应用程序,以反映会话中的此更改。
{
"iss": "https://idp.example.com",
"aud": "s6BhdRkqt3",
"sub": "5be86359073c434bad2da3932222dabe",
"auth_time": "1645784467",
"amr": [ "pwd", "hwk" ],
"acr": "http://schemas.openid.net/pape/policies/2007/06/multi-factor"
"exp": 1645784767,
"iat": 1645784467
}
此新 ID 令牌显示用户:
- 符合特定的 ACR 级别
- 使用密码和 FIDO 密钥进行身份验证( = 硬件安全密钥的拥有证明)
hwk
- 上一次证明了他们的身份是在几秒钟前。
使用此新标识令牌,客户端应用程序可以看到用户已完成递增身份验证,并且可以提升会话。 通过验证声明和时间,他们还可以证明递增身份验证是应他们的请求进行的(即在过去几分钟内)。acr
auth
使用 OAuth 对 API 访问进行升级身份验证
借助 OAuth,您可以实现类似的方法,其中 API 端点需要特定级别的身份验证。 您可以通过要求在授权服务器上通过递增身份验证保护的作用域来实现这一点,或者您可以遵循与 OpenID Connect 类似的方法,再次使用相同的 、 和声明,但这次是作为访问令牌的一部分。 让我们看一下这两种方法。acr
amr
auth_time
使用作用域的 API 递增身份验证
要使用递增身份验证保护 API,可以使用 OAuth 作用域。 此作用域不表示递增身份验证,而是强制授权该作用域需要递增身份验证。
例如,如果您的授权服务器收到针对 API 上特定范围(例如允许转账的作用域)的授权请求,它将要求用户重新进行身份验证或使用其他因素进行身份验证,即使他们已经进行身份验证。
为该范围颁发新的访问令牌后,客户端应用程序就可以访问 API。 API 将验证访问令牌是否具有正确的范围,并且可能还会检查通常的 和/声明以查看最近是否发生了升级身份验证。 还可以颁发授权此范围的访问令牌,其生存期较短。 但是,请记住,此方法不是事务性的;访问令牌可以重复使用。auth_time
acr
amr
此方法与 OpenID Connect 提供的特殊处理范围没有什么不同,在请求刷新令牌时始终需要同意。offline_access
但这种方法需要特殊范围的知识。 如果 API 可以告诉客户端应用程序它需要触发递增身份验证,那不是更好吗? 是的。 是的,会的。
从 API 请求递增身份验证
借助 OAuth 和 OpenID Connect,API 可以使用访问令牌来了解用户如何在身份提供程序中对自己进行身份验证。 这允许 API 做出一些授权决策:访问令牌是否有效,并且用户是否经过足够高的身份验证级别以调用此 API 终结点。
允许 API 本身请求递增身份验证非常强大。 毕竟,API 了解执行其所持有的功能所需的安全级别,并且它是了解用户是否可以调用此端点的人。 因此,它是了解和实施递增身份验证的理想场所。
此方法使用与 OpenID Connect 方法相同的声明和授权请求参数,但采用 API 触发递增身份验证的方式。 此方法使用您已经可用的工具,错误类型由 Vittorio Bertocci 和 Brian Campbell 在 2021 年 OAuth 安全研讨会上提出。 我将使用访问令牌是 JWT 的示例,但这同样适用于其他令牌格式和自检终结点。
初始 API 请求
让我们在用户已进行身份验证后开始,其中客户端应用程序尝试调用要求用户使用增强身份验证的 API:
GET /users/8054568ea46e4e6b8e7a30ca34b18f9a HTTP/1.1
Host: example.com
Authorization: Bearer eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk3NTExZjU2ZjkyYjhjMGY0YjczMDI4NWEyNDQwMGQzIn0.eyJpc3MiOiJodHRwczovL2lkcC5leGFtcGxlLmNvbSIsImF1ZCI6ImFwaTEiLCJzdWIiOiI1YmU4NjM1OTA3M2M0MzRiYWQyZGEzOTMyMjIyZGFiZSIsImNsaWVudF9pZCI6InM2QmhkUmtxdDMiLCJzY29wZSI6InJlYWQiLCJhdXRoX3RpbWUiOiIxNjQ1NzgzODIzIiwiYW1yIjpbInB3ZCJdLCJleHAiOjE2NDU3ODgxNjEsImlhdCI6MTY0NTc4NDU2MSwianRpIjoiYmEwZjg2NDE4Zjc0N2MyNWU1ODg3N2MwMDlmZmYzZGMifQ.1z4SuiOQHGXojDsAYtt0iCVH8QmYQdJzrdb6EmEg9ZyPeUneO2g_3P0OmyvQ5x2hv0VsPF_QhfmKe8zEVEqwYg
但是,此特定 API 终结点(不一定是整个范围或 API)要求用户将自己重新进行身份验证到增强级别。 它甚至可能是他们试图修改的特定数据或帐户。
虽然访问令牌有效,但用户仅使用其用户名和密码进行身份验证。 与 OpenID Connect 的 ID 令牌非常相似,访问令牌显示不可接受的 AMR 值:
{
"iss": "https://idp.example.com",
"aud": "api1",
"sub": "5be86359073c434bad2da3932222dabe",
"client_id": "s6BhdRkqt3",
"scope": "read",
"auth_time": "1645784561",
"amr": [ "pwd" ],
"exp": 1645788161,
"iat": 1645784561,
"jti": "ba0f86418f747c25e58877c009fff3dc"
}
API 返回 401 未经授权,告知客户端应用程序令牌不够好。 由于令牌有效,您可能期望 403 禁止访问,但用户无法完成请求;但是,使用 401,您可以访问标头。WWW-Authenticate
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
error="insufficient_authentication_level",
error_description="A different level of authentication is required",
acr_values="http://schemas.openid.net/pape/policies/2007/06/multi-factor"
在这里,标头告诉客户端应用程序它需要触发递增身份验证,并使用正确的凭据(新令牌)重试。 是信令递增身份验证的建议错误值,虽然令牌有效,但用户需要更好地进行身份验证。 (可选)告知客户端应用程序要在标识提供程序处请求的 ACR 值。WWW-Authenticate
insufficient_authentication_level
acr_values
因此,客户端应用程序必须启动递增身份验证。
基于 API 错误启动递增身份验证
现在客户端了解完成 API 调用需要递增身份验证,它可以向身份提供程序发出适当的授权请求。 多亏了标题,它甚至知道要传递哪个WWW-Authenticate
acr_values
HTTP/1.1 302 Found
Location: https://idp.example.com/authorize?
response_type=code
&resource=api1
&scope=read
&client_id=s6BhdRkqt3
&redirect_uri=https://client.example.org/cb
&acr_values=http://schemas.openid.net/pape/policies/2007/06/multi-factor
这将导致具有更新声明的新访问,显示最近进行了步骤身份验证。
{
"iss": "https://idp.example.com",
"aud": "api1",
"sub": "5be86359073c434bad2da3932222dabe",
"client_id": "s6BhdRkqt3",
"scope": "read",
"auth_time": "1645785105",
"amr": [ "pwd", "hwk" ],
"acr": "http://schemas.openid.net/pape/policies/2007/06/multi-factor"
"exp": 1645788705,
"iat": 1645785105,
"jti": "ba0f86418f747c25e58877c009fff3dc"
}
与标识令牌一样,此新访问令牌显示用户:
- 符合特定的 ACR 级别
- 使用密码和 FIDO 密钥对自己进行身份验证
- 上一次证明了他们的身份是在几秒钟前。
客户端应用程序现在可以使用此新的访问令牌调用 API,向 API 显示用户已完成递增身份验证。 通过验证声明和 ,API 可以证明递增身份验证是应他们的请求进行的(即在过去几分钟内),因此用户有权调用 API。acr
auth_time
客户端应用程序可以继续使用此新的访问令牌调用 API,直到 API 确定用户需要再次重新进行身份验证(通过检查声明)。auth_time
这种方法允许通过开放标准进行无状态递增身份验证,而无需自定义身份验证 API 和铸造任何容易出错的东西,例如部分令牌。
单个事务的递增身份验证
如果您正在寻找一次性令牌,当用户授权特定请求时,这是另一天的不同主题,而不是升级身份验证的一部分。 上述方法不是事务性的。
可以考虑使用 API 记录 (JWT ID) 声明以防止重复使用;但是,我建议查看OAuth的富授权请求(RAR)和英国开放银行使用的实现。jti
奖励:使用 SAML 2.0 进行升级身份验证
您还可以使用 SAML 2.0 触发递增身份验证,再次覆盖 SSO 并强制用户重新进行身份验证。 值得庆幸的是,该方法与OpenID Connect使用的方法几乎相同。
对于服务提供商(客户端应用程序/信赖方),请检查 SAML 响应中返回的断言,而不是检查标识令牌。 让我们看一下 SAML 断言中返回的身份验证语句:
<AuthnStatement xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
AuthnInstant="2022-02-25T09:24:25Z"
SessionIndex="_af23066e-7f08-454a-9fe4-425c04d37aec">
<AuthnContext>
<AuthnContextClassRef>
http://schemas.openid.net/pape/policies/2007/06/multi-factor
</AuthnContextClassRef>
</AuthnContext>
</AuthnStatement>
这包含:
AuthnInstant
,即用户进行身份验证的时间。此属性对应于 OpenID Connect 的声明auth_time
AuthnContextClassRef
,这是用户进行身份验证的方式。此属性对应于 OpenID Connect 的声明。acr
对于请求递增身份验证,您可以在SAML AuthnRequest中使用RequestAuthnContext元素:
<saml2p:AuthnRequest
xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
<!--Other elements (e.g. issuer)-->
<saml2p:RequestedAuthnContext Comparison="exact">
<saml2:AuthnContextClassRef>
http://schemas.openid.net/pape/policies/2007/06/multi-factor
</saml2:AuthnContextClassRef>
</saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>
此请求包括服务提供商需要标识提供者对其用户进行身份验证的 ACR 值。 比较值表示 ACR 级别可以是精确、最小、更好或最大;但是,SAML 互操作配置文件建议您仅使用 exact,这意味着用户必须满足请求的级别之一。
同样,就像OAuth和OpenID Connect一样,双方必须提前就值达成一致,以便服务提供商和身份提供商都理解其含义。AuthnContextClassRef
总结
递增身份验证不需要使用来自应用程序的多个 API 调用和大量文档进行复杂的编排。 相反,通过利用开放标准中已有的功能,您可以为所有应用程序构建低摩擦递增身份验证,并具有它们很可能已经在使用的协议库。