【问题标题】:JWT refresh token. Can it be self contained?JWT 刷新令牌。可以自给自足吗?
【发布时间】:2020-12-24 15:15:40
【问题描述】:

我想保留这样的令牌(有效负载):

access_token

{
  "user": "john_doe",
  "iat": 1444262543,
  "exp": 1444262563, // 15 minutes
  "type": "access"
}

刷新令牌

{
  "user": "john_doe",
  "iat": 1444262543,
  "exp": 1444262563, // 24 hours
  "type": "refresh"
}

如您所见,access_token 和 refresh_token 几乎相同,除了生命周期和类型。

这是个问题吗?以这种方式使用刷新令牌会是安全漏洞吗?

PS:这背后的原因是因为我不想将刷新令牌存储在存储(DB/Redis)中。

【问题讨论】:

    标签: security authentication oauth-2.0 jwt


    【解决方案1】:

    从安全角度来看,自包含刷新令牌很容易受到攻击。

    我们有刷新令牌的原因是为了延长访问令牌的有效期。换句话说,我们访问了一个授权服务器(或者有一个完全不同的授权流程),使用一些公钥验证它并从服务中获取访问令牌(使用私钥创建)。

    刷新令牌必须存储在服务器端。我们不应该利用 JWT 的“自包含”属性来获取刷新令牌。这样做让我们除了更改我们的私钥之外,无法撤销刷新令牌。

    示例:假设我从我的手机登录到您的应用程序并且它丢失了。 该应用程序包含刷新令牌权限,其中包含我的所有信息。如果 refresh_token 没有存储在后端,我们没有任何方法可以使该会话无效。现在需要更改创建令牌的私钥,这不好。

    从 refresh_token 创建 access_token 的高级步骤可能是这样的:

    让我们先打好基础:

    数据库中的tokens表可以包含以下字段:

        user_id<uuid>: id of the user
        access_token<text>: access token
        access_token_expiry<date>: access token expiring timestamp
        refresh_token<text>: refresh token (sha)
        refresh_token_expiry<date>: refresh token expiring timestamp
        refresh_count: number of times the refresh token has been used to access access_token (just in case if you need this field)
    

    请注意,我们应该将刷新令牌的哈希值存储在数据库中。

    此外,一个用户可能有多个会话,比如他们从同一设备上的不同浏览器/不同设备登录到我们的应用程序。为了满足这个需求,我们需要创建一个 login_sessions 表

    id<BIGINT>: primary key for the table
    user_id<uuid>: id of the user who logged in
    map_id<uuid>: This will be the key which maps one access token to its refresh token counterpart. Both tokens of the pair will contain this id in their body.
    status<String>: could be ACTIVE|INACTIVE(logged out, INACTIVE user will not be allowed to get access_token from the refresh_token)
    created_on<Date>: timestamp for record created on
    modified_on<Date>: timestamp for record modified on
    

    refresh_token 正文:

    {
      "iss": "ABC Service", (The Issuer of the token)
      "sub": "4953fag3-ec5e-4ed3-b09e-d847f3f376c6", (The user_id, subject of the token)
      "aud": "UI", (audience who will use the token)
      "typ": "refresh", (type)
      "iat": 1599326501,
      "exp": 1601918501,
      "jti": "749c77e5-bac0-43f1-aeea-1618ada0224f", (unique identifier for this token)
      "mid": "e392692b-6d77-49a9-9928-ac3c3d5208a3" (unique identifier for access-refresh token pair - refers to map_id in login_sessions table)
    }
    

    access_token 正文:

    {
      "iss": "ABC Service", (The Issuer of the token)
      "sub": "4953fag3-ec5e-4ed3-b09e-d847f3f376c6", (The user_id, subject of the token)
      "aud": "UI", (audience who will use the token)
      "typ": "access", (type)
      "iat": 1599326501,
      "exp": 1599412901,
      "jti": "3d2985ef-e767-495e-af88-448fc0ecb167", (unique identifier for this token)
      "mid": "e392692b-6d77-49a9-9928-ac3c3d5208a3", (unique identifier for access-refresh token pair - refers to map_id in login_sessions table)
    }
      "fname": "john",
      "lname": "doe",
      "roles": [99b18377-5b4c-4e68-8ff4-bac4aea93bd2]
      "other_key": "other_value"
    

    从刷新令牌生成访问令牌:

    一旦访问令牌过期,我们点击授权服务器 api 来获取访问令牌,同时在正文中传递 refresh_token (jwt)。 授权服务器将公开此 API,该 API 将接受刷新令牌并执行以下步骤,然后返回访问令牌。

    步骤:

     1. Verify the refresh_token with public key (whose counterpart private formed the token initially)
     2. Pick the mid(map_id) from the token body
     3. Get login session record containing this mid from the login_sessions table. Go to next step if there exists some login_session record.
     4. If the status of the login_session record is ACTIVE, create one access_token (using the private key) with relevant details in it's body
     5. Send back the access token as a response.
    

    如果上述步骤 1-4 中的任何一个失败,我们会发回带有相关消息的错误响应。

    【讨论】:

    • 您能否添加更多关于从访问令牌创建刷新令牌的信息?
    • 嘿 Ivan,我已经更新了关于从刷新令牌创建访问令牌的信息(这就是我们想要的对吗?)。我建议您为此阅读“从刷新令牌生成访问令牌”部分。在这部分之前,我已经为它奠定了基础,这可能会有所帮助。谢谢
    • @IvanStudenikin 刷新令牌不是从访问令牌创建的。从短期令牌(Access)创建长期令牌(刷新)是一个安全问题。刷新令牌用于创建访问令牌和身份令牌。
    • @ManishJuriani 感谢您的解释(非常详细)!问题:既然我们仍然需要求助于数据库,那么使用简单的 Guid 作为 refresh_token 并将其映射到数据库中的 access_token 声明(例如用户名)不是更好吗?
    • @IvanStudenikin 是的,这也很好。您可能需要稍微更改实现。就像不是步骤1. Verify the refresh_token with public key,我们可以从令牌表中验证刷新令牌到期。想知道您对为什么 guid 刷新令牌比 jwt 更好的想法?
    猜你喜欢
    • 2016-08-29
    • 2018-10-01
    • 2021-10-31
    • 2016-03-05
    • 2016-06-25
    • 1970-01-01
    • 2021-04-06
    • 2017-10-11
    • 2016-10-14
    相关资源
    最近更新 更多