OpenID Connect中的ID Token到期时间有什么意图?

119
在 OpenID Connect 中,访问令牌具有过期时间。对于授权代码流程,这通常很短(例如20分钟),之后您可以使用刷新令牌来请求新的访问令牌。 ID 令牌也具有过期时间。我的问题是:这个的意图是什么?
任何 ID 令牌的过期时间小于刷新令牌的过期时间都意味着您最终会拥有一个过期的 ID 令牌,但是有效的访问令牌。
那么你应该:
  • 将ID令牌的过期时间设置得比刷新令牌的过期时间更长,或者
  • 将其设置为与访问令牌相同的过期时间,并在到期时采取某些操作(是什么?),或者
  • 在接收到 ID 令牌后仅在客户端使用它,然后忽略其过期时间?
  • OpenID Connect 规范只是说在验证 ID 令牌时,
    "The current time MUST be before the time represented by the exp Claim."
    

    可能支持上述第三个选项。


    EDIT

    由于OpenID Connect是基于OAuth2构建的,因此可以在OAuth2规范中找到下面附加问题的答案,该规范表示:

    expires_in
         RECOMMENDED.  The lifetime in seconds of the access token.
    

    相关问题是当你用授权码交换令牌时,同一规范称您可能会收到类似以下的响应:


    {
     "access_token": "SlAV32hkKG",
     "token_type": "Bearer",
     "refresh_token": "8xLOxBtZp8",
     "expires_in": 3600,
     "id_token": "eyJhbG[...]"
    }
    

    但在这种情况下,“expires_in”与什么相关?访问令牌,刷新令牌还是ID令牌?

    (请注意,IdentityServer3 将其设置为访问令牌的过期时间)。

    8个回答

    124

    我自己回答了这个问题,因为发现了我的问题背后的一些假设是错误的,所以在这里更容易澄清,而不是重新撰写问题。

    ID令牌旨在向客户端证明用户已通过身份验证,并由此获知他们是谁。

    当客户端接收到ID令牌时,通常会执行类似于将其转换为ClaimsIdentity并持久化的操作,例如使用cookie。

    此时,ID令牌必须未过期(应该是未过期的,因为它刚刚被发布)。 但是在此之后,它不再使用,因此即使用户仍然拥有活动会话,它也不会过期。客户端拥有所需的身份验证信息,并且反过来可以选择其自己的策略,以确定会话在用户必须再次登录之前持续的时间。

    我问问题时的错误假设是ID令牌和访问令牌应该一起使用,因此两者都需要具有有效的到期日期。 由于各种原因,这是错误的:

    • ID令牌仅用于向客户端进行身份验证(如上所述)。
    • 访问令牌与客户端无关。 它们用于访问资源,只有在客户端需要调用资源时才处理它们。
    • 类似于独立的MVC或WebForms应用程序需要ID令牌。 如果它没有调用外部资源,则没有授予访问权限的内容,因此没有访问令牌。

    4
    你有这方面的参考资料吗?Eugenio在他的回答中声称可以刷新ID令牌。这是真的吗? - AndyD
    11
    无法刷新ID令牌以延长其到期时间(就像可以使用离线访问令牌刷新访问令牌一样)。但是,如果您在OpenID Connect提供程序(例如,在IdentityServer3登录后的Cookie)中具有未过期的身份验证会话,则当您重复登录请求时,提供程序可以跳过身份验证(因为Cookie表明您已经完成了身份验证),并返回新的ID令牌(如果请求了,则还包括访问令牌)。当然,这仅在cookie的生存期比ID令牌更长时才有效。 - Appetere
    1
    虽然你“可以”这样做,但我不确定是否正确这样做。这也不会对最终用户产生无缝的体验,因为它需要进行一些浏览器重定向。 - Kir
    3
    您能否编辑您的答案,因为您并没有真正解释Id令牌过期的目的,您只是说在有活动会话时这并不重要(这是正确的)。我的猜测是,Id令牌过期的目的是防止重放攻击。如果某人记录了Auth服务器的流量,那么他可以使用该记录下来的流量欺骗客户端应用程序,让它相信他拥有有效的用户身份。但是,如果客户端检查exp日期,它将看到可能是重放攻击。(nonce也用于同样的目的)。 - CodesInTheDark
    1
    @SAHIL 当您首次接收ID令牌时,它应该是未过期的。如果您接受一个过期的令牌,那么同一个令牌可能会被永久重复使用,这将非常不安全。 - Appetere
    显示剩余8条评论

    50

    我因为个人原因不得不深入研究这个问题并写了一篇文章,所以我会在这里发布我所学到的内容...

    首先,我会回答这个问题,即冒着陈述显而易见的风险:如果当前时间大于过期时间,则不能信任ID令牌及其内容必须被忽略。提问者的答案表明,在用户进行初始身份验证之后,ID令牌不再使用。然而,由于ID令牌是由身份提供者签名的,因此它在任何时候都可以作为一种可靠的方式,向应用程序可能使用的其他服务提供确定用户身份的方法。使用简单的用户ID或电子邮件地址是不可靠的,因为它很容易被欺骗(任何人都可以发送电子邮件地址或用户ID),但由于OIDC ID令牌由授权服务器签名(通常还具有第三方的好处),因此它无法被欺骗,并且是更可靠的身份验证机制。

    例如,移动应用程序可能希望能够告诉后端服务正在使用该应用程序的用户是谁,并且可能需要在初始身份验证后的短暂时期之后执行此操作,此时ID令牌已过期,因此无法用于可靠地验证用户。

    因此,就像访问令牌(用于授权 - 指定用户具有的权限)可以被刷新一样,您能否刷新ID令牌(用于身份验证 - 指定用户是谁)?根据OIDC规范,答案并不明显。在OIDC / OAuth中,有三种获取令牌的“流程”,即授权码流程、隐式流程和混合流程(我将在下面跳过它,因为它是其他两个的变体)。
    对于OIDC / OAuth中的隐式流程,您可以通过将用户重定向到授权端点并将id_token作为response_type请求参数的值来在授权端点请求ID令牌。隐式流程成功验证响应必须包括id_token
    对于身份验证代码流程,当客户端将用户重定向到授权终点时,客户端将code指定为response_type请求参数的值。成功响应包括一个授权码。客户端使用授权码向令牌终点发出请求,并根据OIDC Core Section 3.1.3.3 Successful Token Response 响应必须包含ID令牌
    因此,无论是哪种流程,这就是您最初获取ID令牌的方法,但是如何刷新它呢?OIDC Section 12: Using Refresh Tokens关于刷新令牌响应有以下声明:

    在刷新令牌成功验证后,响应正文是第3.1.3.3节的令牌响应,除了可能不包含id_token

    它可能不包含ID Token,因为没有指定强制包含ID Token的方法,您必须假设响应不包含ID Token。因此,在使用刷新令牌“刷新”ID Token方面,技术上没有指定的方法。因此,获取新的ID Token的唯一方法是通过将用户重定向到授权终点并启动隐式流或身份验证代码流来重新授权/认证用户。OIDC规范确实添加了一个prompt请求参数authorization request,以便客户端可以请求授权服务器不要提示用户进行任何UI操作,但仍必须发生重定向。


    如果您正在编写一个通用软件来与任意授权提供程序配合使用,那么您不能依赖于从刷新中返回id_token。但是,如果您正在使用特定的提供程序(例如IdentityServer4),则可以检查其功能,并在刷新请求后使用接收到的id_token。 - Michael Freidgeim
    那么如何刷新id_token呢? - jwilleke
    据我所知,如上所述,“获取新的ID令牌的唯一方法是通过将用户重定向到授权端点来重新授权/验证用户”。 - Scott Willeke
    @MichaelFreidgeim 有趣,您是指通过 Open ID Connect Discovery mechanism 吗?我们该如何实现呢? - Scott Willeke
    @ScottWilleke,我不确定,它是否是通过发现公开的。我正在使用特定的提供程序identityServer4进行工作,并且从文档中我知道,id_token是从刷新返回的。您可以在代码中检查它是否已返回,如果没有,则实施一些回退方案。 - Michael Freidgeim
    1
    关于“刷新响应体可能不包含id_token”的好回答。点赞。顺便说一下,我的理解是OIDC规范确实留有余地,可以使用刷新令牌来获取新的ID令牌:客户端可以通过将“id_token”指定为其中一个作用域来这样做;但是在这里仍然需要谨慎,因为认证服务器最终决定是否满足您请求的范围。 - RayLuo

    12
    如果我理解正确的话,根据这个链接OpenID Connect Core 1.0规范,可以将ID令牌本身存储在cookie中作为持续会话的机制,并随每个需要认证的请求发送给客户端。然后,客户端可以通过本地或提供者的验证器端点(如果提供的话,例如Google)验证ID令牌。如果令牌已过期,则应该进行另一个认证请求,但是这次URL参数中要包含prompt=none。还要确保在id_token_hint参数中发送过期的ID令牌,否则提供者可能会返回错误。
    因此,ID令牌过期似乎很自然,但prompt=none确保可以无需用户干预顺利获取新的ID令牌(除非用户已注销该OpenID)。

    6

    意图是相同的:在id_token过期后,您无法使用它。主要区别在于,id_token是一种数据结构,您不需要调用任何服务器或端点,因为信息已编码在令牌本身中。常规的access_token通常是一个不透明的工件(如GUID)。

    id_token的消费者必须始终验证其(时间)有效性。

    我对IS不太熟悉,但我猜它是一个方便的字段。您应该始终检查exp声明。

    过期只是其中一种验证方式。 id_token还进行数字签名,这也是您必须执行的验证之一。


    1
    谢谢Eugenio。我主要的问题是当ID令牌过期时应该怎么做?我认为(可能是错误的),为了更新短期访问令牌,你必须使用刷新令牌。但如果ID令牌与访问令牌具有相同的到期时间,那么你将立即拥有一个过期的ID令牌,因此刷新访问令牌似乎毫无意义。我想我可能在这里漏掉了什么! - Appetere
    1
    您可以使用(未被撤销的)refresh_token 获取新的 access_token 或 id_token。或者要求用户再次登录。id_token 在逻辑上等价于 access_token,只是格式不同。 - Eugenio Pace
    2
    我最新的理解是,当用户与授权服务器有经过身份验证的会话时,当访问令牌过期时,401 => 302 重定向到授权服务器将获得新的访问和 ID 令牌,而无需用户干预。但在离线模式下,refresh_token 只会返回一个新的 access_token,它表示特定用户被允许访问某些资源。它不能返回 id_token,因为这将意味着特定用户已经通过身份验证,在离线模式下这种情况并不成立。 - Appetere
    这将是回答有关id_token和access_token之间的区别(特别是在使用opaque / reference tokens时)的问题的好答案。首先重点回答问题,然后澄清如何使用访问令牌和ID令牌。 - Trent

    6
    刷新令牌意味着您可以再次使用它来向授权服务器(在本例中为OP-OpenID-Connect提供程序)请求某些内容,即使用户未登录。通常,您仅允许有限的资源进行此操作,并且仅在用户已经登录并至少通过身份验证一次后才能进行。刷新令牌本身也应该有时间限制。
    在OIDC的隐式流程中,您调用授权终端点,并在响应中接收到ID令牌以及其中所有权限和声明信息。对API的后续调用应该使用代码流完成。隐式流程旨在启用仅JavaScript或仅浏览器的应用程序,而不是与服务器交互的应用程序。因此,即使有一种“刷新”此令牌的方法,出于安全考虑,您也不应该让其存在太久。它将被窃取并被未经授权的用户冒充ID重复使用。您应该强制重新登录。
    在代码流程中,您调用OP的授权终端点,并接收到授权代码(也称为授权令牌或authcode)。出于相同的原因,这应该类似于您在隐式流程中收到的id_token,并且不能和不应该被更新。
    然后,您的UI或应用程序调用OP的令牌终端点,并在进一步获得用户同意的情况下(通过UI允许在OP服务器上使用其拥有的资源),同时接收以下内容:
    1. 用于身份验证的id_token-除了在注销期间作为提示时,不应再次在服务器调用中使用,当其到期时间不再重要时,因此,出于上述原因,应该让其过期,并且永远不会刷新。 2. 访问令牌-稍后,在调用API时,可以将其提供给OP的UserInfo终端点。这将返回声明和API可以相应授权的资源(按范围和每个范围的声明)。
    您可以刷新此访问令牌,因为它仅告诉API用户具有哪些声明以及用户同意向您提供哪些资源(按范围和每个范围的声明)。如上所述,这是为了允许即使用户不再登录也能访问。当然,您永远不希望允许刷新id_token,因为您不希望在未经登录的情况下允许冒充。

    2
    你关于隐式流的说法部分是不正确的。使用隐式流的客户端可以获取访问令牌和身份令牌,并可以使用该访问令牌与服务器进行交互。 - Shaun Luttin
    有一种常见的做法,即当id_token过期时,客户端从服务器请求新令牌,以便用户无需再次授权。 例如,请参见 https://damienbod.com/2017/06/02/implementing-a-silent-token-renew-in-angular-for-the-openid-connect-implicit-flow/ - Michael Freidgeim

    5
    我原本想将这个答案作为评论发布,但由于我在StackOverflow上的活跃度不高,所以我认为我应该将它作为备选答案发布。
    当您尝试注销会话时,您还可以使用 id_token 作为id_token_hinthttp://openid.net/specs/openid-connect-session-1_0.html。我真的认为,如果您只关心注销特定用户,那么此时id_token是否过期并不重要。

    4

    简述;

    在信任 ID 令牌所说的内容之前,请验证它。

    更多细节

    OpenID Connect 中的 ID 令牌到期时间的目的是什么?

    其目的是允许客户端验证 ID 令牌,而客户端必须在使用 ID 令牌信息的操作之前验证 ID 令牌。

    来自OpenID 隐式流规范

    如果该文档中定义的任何验证程序失败,则必须中止需要使用未能正确验证的信息的任何操作,并且不得使用未能验证的信息。

    为了证实这一点,Google 的 OpenID Connect 文档关于 ID 令牌验证的说明如下:

    ID 令牌有用的一个原因是你可以将它们传递给应用程序的不同组件。这些组件可以将 ID 令牌作为轻量级身份验证机制,对应用程序和用户进行身份验证。但是,在您可以使用 ID 令牌中的信息或依赖它作为用户已验证的断言之前,必须验证它。

    因此,如果我们的客户端应用程序将基于 ID 令牌的内容采取某些操作,则必须再次验证 ID 令牌。


    1

    我想分享我的经历。现在是2021年6月。我写这篇文章是因为我陷入了第三方身份验证业务。我是一名资深程序员,但对安全却是新手。换句话说,所有的标准、规范和术语都是陌生的,任何人都可以在这个领域击败我。请原谅我没有使用所有的术语。

    不废话了,我正在编写一个Angular/Node应用程序,所以UI=Angular,API(API服务器)=Node/Express。我不想创建自己的用户名/密码验证,而是转向第三方身份验证,让他们验证用户所声称的真实性。以下是对我非常重要的两本指南:

    1. 使用JSON Web Tokens(JWT)进行Angular身份验证:完整指南
    2. Eiji的后端服务器身份验证
    结合No.1和angularx-social-login,我已经将UI与Google连接起来,然后将idToken附加到API中,大功告成!使用本地库API遵循No.2可以验证idToken,太棒了!
    等等,idToken在1小时内会过期。那么我该如何刷新它?
    我的理解是,我只需要谷歌的认证,我不关心他们使用的标准和版本,但像其他人一样,我只信任他们的认证。认证基本上是验证他们声称自己是谁。授权是用户可以做什么/去哪里的访问控制。内部访问控制(允许用户做什么)未暴露给谷歌,他们对此一无所知。因此,accessToken应该不在考虑范围内。对吗?

    我花了几天时间研究如何刷新idToken,现在得出结论:Google不建议这样做,也没有angularx-social-login提供方法。在第2点中,Eiji已经明确说明enter image description here

    因此,我遇到的情况的解决方案是:

    • 使用Google的身份验证。
    • 在API的初始验证idToken后创建自己的会话管理/超时规则以缓解exp
    • 最好将此会话数据添加到cookie中,使用res.cookie("SESSIONID", myOwnID, {httpOnly:true, secure:true});

    为了更好的保护,Eiji还推荐跨账户保护。希望这能帮助到某些人!


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接