V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
logto
V2EX  ›  程序员

了解 OpenID Connect (OIDC) 协议中的 refresh token, access token 和 ID token

  •  
  •   logto ·
    logto-io · 2023-08-30 16:13:46 +08:00 · 1915 次点击
    这是一个创建于 452 天前的主题,其中的信息可能已经有所发展或是发生改变。

    简介

    OpenID Connect 协议,也称为 OIDC ,已成为广泛采用的标准,为身份管理提供了基本框架。 它是一个建立在 OAuth 2.0 协议之上的身份验证层。OAuth 2.0 仅用于资源授权,而 OIDC 通过新引入的 ID token 来标准化和加强身份认证。

    我们的产品 Logto 是一个的客户身份和访问管理( CIAM )解决方案,正是构建在 OIDC 协议之上。在我们的社区中,用户经常问到一个问题:

    Refresh token, access token 和 ID token 之间有什么区别?

    在本文中,我们将探讨这些 token 的含义,同时阐明 Logto 如何增强整体开发者体验。

    从一个实际场景开始

    假设你正在处理一个典型的客户端 - 服务器应用,它们通过 RESTful API 进行通信;你还有一个可用的 OIDC 服务。对于私有 API ,你希望只有经过授权的客户端才能访问。马上就会有一个问题出现:

    为保护我的 API ,我需要哪种类型的 token ?

    快速答案是:access token 。

    在 Resource Indicators for OAuth 2.0 (RFC 8707) 中,每个受保护的 API 都被视为一个「资源」。Access token 是客户端在请求 API 资源时传输到服务器的凭据(可以想象成一把钥匙),通常通过请求 header 进行传递。在服务器端,每当收到请求时,服务器只需验证传入请求是否携带有效的 access token 。

    ℹ️ Access token 是一种短期凭据,用于授权用户访问受保护的资源。当用户使用 OIDC 成功进行认证时,授权服务器会颁发 access token 。在 Logto 中,access token 会以 JSON Web token (JWT) 的形式返回。 该 token 包含有关用户、其权限以及其有效期的信息。你可以将其附于每个后续请求一起发送到资源服务器,以便资源服务器验证用户是否有权访问该资源。

    对于包含签名的 access token ,资源服务器甚至可以在不请求授权服务器的情况下验证其有效性(离线验证)。这极大地提高了性能,因为资源服务器不必在每次请求时都与授权服务器进行通信;其安全性也得到了保证,因为签名的 access token 无法被篡改。

    🤔️ JWT 签名与验证涉及到一些复杂的数学运算和密码学概念,这超出了本文的范围。我们之后可能会撰写一篇文章来详细介绍这个话题。

    然而,你可会想:如果我的客户端应用程序在成功登录后可以获得有效的 access token ,并使用该 access token 请求服务器 API ,那不就足够了吗?为什么我还需要 refresh token 和 ID token ?

    这是一个很好的问题,让我们一个个来。

    为什么我们需要 refresh token ?

    尽管技术上 access token 满足使系统正常工作的最低要求,但出于安全考虑,access token 的有效期通常很短(通常为一小时)。因此,想象一下,如果我们只有 access token ,最终用户将不得不在每次 access token 过期时重新进行身份验证。对于现代单页 Web 应用程序,特别是移动应用程序来说,经常性地登出是一种相当痛苦的用户体验,尽管我们只是在努力保护他们的用户安全性。

    因此,我们需要在 token 安全性和用户便利性之间取得平衡。这就是 OAuth 2.0 引入 refresh token 的原因。

    ℹ️ Refresh token 是一种较长寿命的 token 。当 access token 过期或需要更新时,可以使用 refresh token 在无需用户重新认证的情况下获取新的 access token 。与 access token 相比,refresh token 的有效期较长,通常持续几天、几周甚至几个月。

    为什么 refresh token 可以具有较长的生命周期?

    Access token 用于访问 API 资源,因此其较短的有效期有助于减少泄漏或受损的风险。Refresh token 有以下特点:

    • Refresh token 仅用于交换新的 access token ,它们不像 access token 那样频繁使用,因此暴露的风险降低。
    • Refresh token 无法直接访问资源,当发现它们被泄露时,授权服务器可以立即撤销它们。

    因此,refresh token 的较长有效期被认为是可以接受的。

    确保 refresh token 的安全性

    由于 refresh token 也存储在客户端,确保它们不被破坏是有挑战的,尤其是对于公共客户端,如单页 Web 应用程序( SPA )。

    在 Logto 中,refresh token 默认启用了自动的 轮换机制,这意味着授权服务器将在满足以下条件时发行新的 refresh token:

    • 单页应用程序:一种公开且不可信的客户端,这些应用会在每次客户端请求 token 时进行 refresh token 轮换。
    • Native 和传统 Web 应用程序:当达到其 TTL 的 70% 时会自动更新(了解更多)。

    虽然你仍然可以在 Logto Console 的应用程序详细信息页面上禁用 refresh token 轮换,但我们强烈建议保留这项安全措施。

    ID token

    ID token 使客户端能够在不请求服务器的情况下获得基本用户信息。

    ℹ️ ID token 是 OIDC 引入的新 token 类型,它包含缓存的用户认证信息,提高了性能和终端用户体验。与只能被资源服务器理解的 access token 不同,ID token 对客户端友好。当客户端发起 OIDC token 交换请求时,可以请求一个 ID token 和一个 access token 。

    ID token 的表现形式为 JSON Web token ( JWT ),其中包含用户声明( claims ),如用户名、电子邮件、头像图片等。它由授权服务器签名,可以由客户端离线验证和解码。 这对于 SPA 和 native 应用程序特别有用,客户端可以缓存 ID token 并使用它来确定用户的身份验证状态,而不用依赖于服务器。

    还需要注意的是,ID token 不用于授权访问受保护的资源(可以想象成身份证); access token 才是这个角色的合适人选。

    🔑 在实践中,我们看到有些开发者在 access token 中包含大量用户声明,这是不推荐的。如果能直接用钥匙开门,为什么要把身份证也带在身上呢?

    我们的 SDK 中使用 ID token 来帮助确定客户端上的身份验证状态。例如,在我们的 JS SDK 中:

    const isAuthenticated = Boolean(await this.getIdToken());
    

    然而,正如上面提到的,客户端 SDK 的 isAuthenticated 标志仅是一个客户端缓存的状态。实时的身份验证状态仍然需要通过 refresh token 和 access token 来确定。

    我们还在 SDK 中提供了一个 getUserClaims() 函数,以帮助解码 ID token 并获取用户声明。如果您想从 OIDC 服务器获取全面和实时的用户信息,可以使用 fetchUserInfo()

    Logto 的优势和功能

    Logto 和附带的 SDK 严格遵循 OIDC 协议,同时为开发者和企业提供了额外的功能和优势:

    1. 便捷的 SDK:Logto SDK 可大幅简化 token 管理。你不必手动存储 refresh token 和 access token 。只需每次调用 getAccessToken(),SDK 将始终尝试重用已存储的 access token ,或在其过期时自动通过 refresh token 更新它。
    2. Refresh token 轮换:Logto 默认开启 refresh token 轮换机制,确保在 refresh token 被使用后始终对其进行更新,从而减轻泄漏或受损的风险。你仍然可以在管理员控制台中禁用它,但我们强烈建议保持启用。
    3. 性能优良:借助 ID token ,你可以在不请求服务器的情况下获取基本用户信息。利用 Logto SDK 提供的 isAuthenticated 状态和 getIdTokenClaims() 函数,就可以在客户端上检查用户的身份验证状态并解码用户声明。
    4. 增强的安全性:Logto 使用 HTTPS 和 TLS 在客户端与授权服务器之间强制实施安全连接。这可以保护 token 免受未经授权的第三方潜在盗窃,并确保你的系统安全。

    小结

    在 OIDC 协议中,refresh token ,access token 和 ID token 共同提供了安全且无缝的用户身份验证体验。

    • Refresh token 消除了用户为获取新 access token 而频繁登录的问题。
    • Access token 可以访问受保护的资源。
    • ID token 在客户端上提供缓存的用户信息,提升了性能。

    了解这些 token 的角色和用法对于采用 OIDC 进行身份验证的开发者来说至关重要,希望大家评论中分享你的想法和经验;同时,也欢迎大家试用我们的产品:

    网站 https://logto.io GitHub https://github.com/logto-io

    9 条回复    2023-09-11 14:15:19 +08:00
    lanlanye
        1
    lanlanye  
       2023-08-30 19:45:09 +08:00 via iPhone
    > 由于 refresh token 也存储在客户端,确保它们不被破坏是有挑战的,尤其是对于公共客户端,如单页 Web 应用程序( SPA )。

    在 OAuth 2.0 中,刷新令牌( refresh_token )是用于获取新的访问令牌( access_token )的凭证。根据 OAuth 2.0 规范,refresh_token 是下发给授权服务器的授权客户端的,而不是直接给终端用户。

    另外我不明白把基本的用户信息也放进 access_token 有什么不好?
    liberty1900
        2
    liberty1900  
       2023-08-30 22:28:49 +08:00
    所以 Auth0, Kinde, Logto 到底哪家强
    qfdk
        3
    qfdk  
       2023-08-31 08:09:43 +08:00 via iPhone
    所以这是算强行推广 ?
    qfdk
        4
    qfdk  
       2023-08-31 08:16:16 +08:00 via iPhone
    @lanlanye 其实我是这么理解的,id token 带的是身份信息. 因为这个 OIDC 是做了一个鉴权的操作 所以要颁发一个 id token. 而 at 是 oauth2.0 授权的操作, 里面按道理是包含着权限信息 是否具有 比如访问 email phone number 这样的信息. 当 api 需要请求某些受保护的资源,只要带上 at 来验证你是否有权限来访问资源, 不管你是谁.
    综上所述,at 里面带用户信息也不是不可以 就是没必要. 获取用户信息有专门的 endpoint 叫做 userinfo endpoint. 比如/oauth/me 这样的接口
    litchinn
        5
    litchinn  
       2023-08-31 10:08:57 +08:00
    @lanlanye 这个好与不好其实本质谈论的还是 jwt 的好与不好,各有理由

    @qfdk oauth2 中 access_token 可以有 opaque token 和 jwt ,使用 jwt 的情况下就包含了基本用户信息,OIDC 中就使用了 jwt 作为 access_token ,当然服务端需要提供 jwks_uri
    https://www.rfc-editor.org/rfc/rfc9068.html
    qfdk
        6
    qfdk  
       2023-08-31 10:47:27 +08:00
    @litchinn #5 我说的是在 opaque 的情况, 忘记说明了. 那个 jwks_uri 对,每次请求的时候会自动访问这个 uri 来验证 jwt 签名
    demonzoo
        7
    demonzoo  
       2023-09-02 09:56:23 +08:00
    @lanlanye token 是发给客户端的没错,现在 oidc 的 token 也是发给客户端的,只不过当今的客户端变成了终端用户手机中的 app 或者浏览器中运行的 SPA.
    另外 access token 里面放用户信息不是不可以,但 auth server 并不需要,它并不关心你是谁,只关心你这把钥匙是不是有效。另外,access token 里面每次请求都要带着,如果里面放了太多冗余的信息,这本身是一种网络资源的浪费,因为 jwt 里面信息多了之后 token 长度会变得很长(当然这也跟签名算法有关,有的签名算法会导致 token 特别长)
    demonzoo
        8
    demonzoo  
       2023-09-02 10:03:40 +08:00
    @litchinn 也不是吧,即使是 jwt ,里面跟用户可能有关系的 token claim 也就只可能是 sub 了(有可能是用户 id )
    Oktfolio
        9
    Oktfolio  
       2023-09-11 14:15:19 +08:00
    4.1.2. "sub" (Subject) Claim

    The "sub" (subject) claim identifies the principal that is the
    subject of the JWT. The claims in a JWT are normally statements
    about the subject. The subject value MUST either be scoped to be
    locally unique in the context of the issuer or be globally unique.
    The processing of this claim is generally application specific. The
    "sub" value is a case-sensitive string containing a StringOrURI
    value. Use of this claim is OPTIONAL.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2955 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 13:10 · PVG 21:10 · LAX 05:10 · JFK 08:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.