【问题标题】:Best practices for server-side handling of JWT tokens [closed]服务器端处理 JWT 令牌的最佳实践 [关闭]
【发布时间】:2015-08-11 22:42:02
【问题描述】:

(源自this thread,因为这确实是其自身的问题,而不是特定于 NodeJS 等)

我正在实现一个带有身份验证的 REST API 服务器,并且我已经成功实现了 JWT 令牌处理,以便用户可以使用用户名/密码通过 /login 端点登录,在该端点上从服务器密钥生成 JWT 令牌和返回给客户。然后在每个经过身份验证的 API 请求中将令牌从客户端传递到服务器,然后使用服务器机密来验证令牌。

但是,我正在尝试了解有关应如何以及在何种程度上验证令牌的最佳实践,以构建真正安全的系统。 “验证”令牌究竟应该涉及什么?可以使用服务器秘密验证签名是否足够,或者我是否还应该针对存储在服务器中的某些数据交叉检查令牌和/或令牌有效负载?

基于令牌的身份验证系统仅与在每个请求中传递用户名/密码一样安全,前提是获取令牌与获取用户密码相同或更难。但是,在我看到的示例中,生成令牌所需的唯一信息是用户名和服务器端密码。这是否意味着假设恶意用户一分钟了解服务器机密,他现在可以代表 any 用户生成令牌,从而不仅可以访问一个给定的用户是否获得了密码,但实际上是所有用户帐户?

这让我想到了问题:

1) JWT 令牌验证是否应该仅限于验证令牌本身的签名,仅依靠服务器机密的完整性,还是伴随单独的验证机制?

  • 在某些情况下,我看到了令牌和服务器会话的组合使用,在通过 /login 端点成功登录后,会建立会话。 API 请求验证令牌,并将令牌中的解码数据与会话中存储的一些数据进行比较。然而,使用会话意味着使用 cookie,并且在某种意义上它违背了使用基于令牌的方法的目的。这也可能给某些客户带来问题。

  • 1234563通过 /login 端点生成的将被接受。这是合理的还是只是多余/过度杀伤?

2) 如果 JWT 签名验证是验证令牌的唯一手段,这意味着服务器机密的完整性是断点,那么服务器机密应该如何管理?从环境变量中读取并在每个部署的堆栈中创建(随机?)一次?定期更新或轮换(如果是这样,如何处理在轮换之前创建但需要在轮换后验证的现有有效令牌,如果服务器在任何给定时间保持当前和以前的秘密就足够了) ?还有什么?

当谈到服务器机密被泄露的风险时,也许我只是过于偏执,这当然是一个更普遍的问题,需要在所有加密情况下解决......

【问题讨论】:

  • 有很多很好的问题。回复:问题 2。我对保存在服务器端的任何密钥都有同样的问题。如果您正在执行任何类型的哈希匹配或非对称解密, - 无论是签署 jwt 还是解密存储在 db 中的 cc 信息,您都必须有一个可由服务器上的代码访问的密钥。那你到底把它放在哪里??这是我找到的最佳答案:pcinetwork.org/forum/index.php?threads/… - 可能与 jwt 密钥一样安全。
  • jwt 令牌中的密钥是什么?我认为 jwt 令牌本身就是一个秘密。或者密钥可以是RSAPrivateKey privateKey ??
  • 这是不久前问的,但也许有人会觉得它有用。就我而言,每个用户都有一个“密钥”。因此,每次用户登录时,我都会生成该密钥并将用户记录存储在数据库中。我使用该秘密验证令牌。注销后,我清除该值。这会自动使之前创建的其他令牌无效(这就是我需要的)。

标签: security authentication token jwt secret-key


【解决方案1】:

我也一直在为我的应用程序使用令牌。虽然我无论如何都不是专家,但我可以分享我对此事的一些经验和想法。

JWT 的要点本质上是完整性。它为您的服务器提供了一种机制来验证提供给它的令牌是真实的并且是由您的服务器提供的。通过您的秘密生成的签名就是为此提供的。所以,是的,如果你的秘密以某种方式泄露,那个人可以生成你的服务器认为是它自己的令牌。仅仅因为签名验证,基于令牌的系统仍然比您的用户名/密码系统更安全。在这种情况下,如果有人知道你的秘密,那么你的系统还有其他安全问题需要处理,而不是制造假令牌的人(即便如此,只需更改秘密即可确保使用旧秘密制作的任何令牌现在都无效)。

至于有效负载,签名只会告诉您提供给您的令牌与您的服务器发出时完全相同。验证有效负载内容是否有效或适合您的应用程序显然取决于您。

对于您的问题:

1.) 根据我有限的经验,使用第二个系统验证您的令牌肯定会更好。简单地验证签名仅意味着令牌是使用您的秘密生成的。将任何创建的令牌存储在某种数据库(redis、memcache/sql/mongo 或其他存储)中是确保您只接受服务器创建的令牌的绝佳方式。在这种情况下,即使您的秘密被泄露,也无关紧要,因为任何生成的令牌无论如何都不会有效。这是我在我的系统中采用的方法 - 所有生成的令牌都存储在数据库(redis)中,并且在每个请求中,我在接受之前验证令牌是否在我的数据库中。这种方式可以出于任何原因撤销令牌,例如以某种方式释放到野外的令牌、用户注销、密码更改、秘密更改等。

2.) 这是我没有太多经验的东西,而且我仍在积极研究,因为我不是安全专业人士。如果您找到任何资源,请随时在此处发布!目前,我只是使用从磁盘加载的私钥,但显然这远非最佳或最安全的解决方案。

【讨论】:

  • 第二点是一个很好的答案:security.stackexchange.com/questions/87130/…
  • 由于令牌在标头中可用,如果令牌被盗并且恶意尝试使用该令牌登录(知道用户的电子邮件地址)怎么办?
  • 如果你存储每个 JWT,那么 JWT 没有任何好处,你还不如坚持使用随机会话 id。
【解决方案2】:

我不认为我是专家,但我想分享一些关于 Jwt 的想法。

  • 1:正如 Akshay 所说,最好有第二个系统来验证您的令牌。

    a.:我处理它的方式:我将生成的哈希存储到会话存储中,并带有到期时间。要验证令牌,它需要由服务器颁发。

    b.:至少有一件事必须检查所使用的签名方法。例如:

    header :
    {
      "alg": "none",
      "typ": "JWT"
    }
    

一些验证 JWT 的库会在不检查哈希的情况下接受这个。这意味着,在不知道你用来签署令牌的盐的情况下,黑客可以授予自己一些权利。始终确保不会发生这种情况。 https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/

c.:使用带有会话 ID 的 cookie 对验证您的令牌没有用处。如果有人想劫持 lambda 用户的会话,他只需要使用嗅探器(例如:wireshark)。该黑客将同时拥有这两种信息。

  • 2:每个秘密都是一样的。总有办法知道它。

我处理它的方式与第 1.a 点有关。 :我有一个与随机变量混合的秘密。每个令牌的秘密都是唯一的。

但是,我正在尝试了解最佳做法 以及应在多大程度上验证令牌,才能真正做到 安全系统。

如果您希望尽可能获得最佳安全性,则不应盲目遵循最佳做法。最好的方法是了解你在做什么(我认为当我看到你的问题时就可以了),然后评估你需要的安全性。如果摩萨德想要访问您的机密数据,他们总能找到方法。 (我喜欢这篇博文:https://www.schneier.com/blog/archives/2015/08/mickens_on_secu.html

【讨论】:

  • 每个令牌都有一个唯一的秘密很好,但是你如何每次都创建一个唯一的秘密?我正在使用 nimbus jwt 库
  • 可能使用了您用户的哈希密码。
  • “如果你做事的方式与其他人不同,那么人们就很难找到通过你的安全的方法。”对我来说,这听起来像是通过默默无闻的安全。之所以称为最佳实践,是因为它们以实用的方式降低了最常见的风险。
  • @Mnebuerquo 我完全同意你的观点,写这篇文章的人不应该被信任;-)
  • 他是正确的,但不应盲目地遵循最佳实践。最好理解为什么最佳实践被认为是最佳。在每个安全设计决策中,都需要在安全性和可用性之间进行权衡。了解原因意味着您可以明智地做出这些决定。 (但请继续遵循最佳做法,因为您的用户不会这样做。)
【解决方案3】:

在您的应用程序中实施 JWT 时需要考虑以下几点:

  • 保持您的 JWT 生命周期相对较短,并在服务器上管理它的生命周期。如果您不这样做,并且以后需要在您的 JWT 中提供更多信息,则您必须支持 2 个版本,或者等到您的旧 JWT 过期,然后才能实施您的更改。如果只看jwt中的iat字段,忽略exp字段,就可以在服务器上轻松管理。

  • 考虑在您的 JWT 中包含请求的 url。例如,如果您希望 JWT 用于端点 /my/test/path,请在 JWT 中包含类似 'url':'/my/test/path' 的字段,以确保它仅在此路径中使用。如果您不这样做,您可能会发现人们开始在其他端点使用您的 JWT,即使是不是为它们创建的端点。你也可以考虑包含一个 md5(url) ,因为在 JWT 中有一个大的 url 最终会使 JWT 变得更大,并且它们可以变得相当大。

  • 如果 JWT 在 API 中实现,则每个用例都应该可以配置 JWT 到期。例如,如果您有 10 个端点用于 JWT 的 10 个不同用例,请确保您可以让每个端点接受在不同时间过期的 JWT。这使您可以比其他端点更多地锁定某些端点,例如,如果一个端点提供的数据非常敏感。

  • 与其简单地在一段时间后使 JWT 过期,不如考虑实施同时支持两者的 JWT:

    • N 次使用 - 在过期前只能使用 N 次,并且
    • 在一定时间后过期(如果您有一个仅供使用的令牌,您不希望它在不使用的情况下永远存在,是吗?)
  • 所有 JWT 身份验证失败都应生成一个“错误”响应标头,说明 JWT 身份验证失败的原因。例如“已过期”、“没有使用情况”、“已撤销”等。这有助于实施者了解其 JWT 失败的原因。

  • 考虑忽略 JWT 的“标头”,因为它们会泄露信息并为黑客提供一定程度的控制。这主要与标头中的 alg 字段有关 - 忽略这一点并假设标头是您想要支持的,因为这样可以避免黑客尝试使用 None 算法,该算法会删除签名安全检查。

  • JWT 应该包含一个标识符,详细说明哪个应用程序生成了令牌。例如,如果您的 JWT 由 2 个不同的客户端 mychat 和 myclassifiedsapp 创建,则每个客户端都应在 JWT 的“iss”字段中包含其项目名称或类似名称,例如"iss":"mychat"

  • 不应将 JWT 记录在日志文件中。可以记录 JWT 的内容,但不能记录 JWT 本身。这可以确保开发人员或其他人无法从日志文件中获取 JWT 并对其他用户帐户进行操作。
  • 确保您的 JWT 实现不允许“无”算法,以避免黑客在未签名的情况下创建令牌。忽略 JWT 的“标头”可以完全避免此类错误。
  • 强烈考虑在您的 JWT 中使用 iat(发布于)而不是 exp(到期)。为什么?由于iat 基本上表示 JWT 的创建时间,这允许您在 JWT 过期时根据创建日期在服务器上进行调整。如果有人通过 exp 那是 20 年后的未来,JWT 基本上永远存在!请注意,如果 JWT 的 iat 在未来,您会自动使 JWT 过期,但要留出一点回旋余地(例如 10 秒),以防客户端时间与服务器时间略有不同步。
  • 考虑实现一个端点以从 json 有效负载创建 JWT,并强制所有实现客户端使用此端点来创建其 JWT。这确保您可以通过在一个地方轻松创建 JWT 来解决您想要的任何安全问题。我们并没有直接在我们的应用程序中执行此操作,现在必须慢慢推出 JWT 服务器端安全更新,因为我们的 5 个不同的客户端需要时间来实施。此外,让您的创建端点接受一组 json 有效负载以供 JWT 创建,这将减少您的客户端进入此端点的 http 请求数。
  • 如果您的 JWT 将用于也支持会话使用的端点,请确保您没有在您的 JWT 中放入满足请求所需的任何内容。如果在未提供 JWT 的情况下确保端点与会话一起工作,则可以轻松做到这一点。
  • 因此,一般而言,JWT 最终会包含某种 userId 或 groupId,并允许根据此信息访问系统的一部分。确保您不允许应用程序某个区域的用户冒充其他用户,尤其是当这提供对敏感数据的访问权限时。为什么?好吧,即使您的 JWT 生成过程只能由“内部”服务访问,开​​发人员或其他内部团队也可以生成 JWT 来访问任何用户的数据,例如某个随机客户公司的 CEO。例如,如果您的应用程序为客户提供对财务记录的访问,那么通过生成 JWT,开发人员可以获取任何公司的财务记录!如果黑客以任何方式进入您的内部网络,他们也可以这样做。
  • 如果您要允许以任何方式缓存包含 JWT 的任何 url,请确保 url 中包含不同用户的权限,而不是 JWT。为什么?因为用户最终可能会得到他们不应该得到的数据。例如,假设一个超级用户登录到您的应用程序,并请求以下 url:/mysite/userInfo?jwt=XXX,并且该 url 被缓存。他们注销,几分钟后,普通用户登录到您的应用程序。他们将获得缓存的内容 - 包含有关超级用户的信息!这往往在客户端发生的较少,而在服务器上发生的更多,尤其是在您使用像 Akamai 这样的 CDN 并且您让某些文件的寿命更长的情况下。这可以通过在 url 中包含相关的用户信息并在服务器上验证这一点来解决,即使对于缓存的请求,例如 /mysite/userInfo?id=52&jwt=XXX
  • 如果您的 jwt 旨在用作会话 cookie,并且只能在创建 jwt 的同一台机器上工作,您应该考虑在 jwt 中添加 jti 字段。这基本上是一个 CSRF 令牌,可确保您的 JWT 不会从一个用户的浏览器传递到另一个用户的浏览器。

【讨论】:

  • 你所说的created_by,在JWT中已经有一个声明,它被称为iss(发行人)。
  • 是的,好点 - 我会更新...谢谢!
  • 如果您需要在用例中将频繁更新的数据放入 JWT 有效负载的字段中,那么第一点很有用(因此您需要定期刷新令牌)。我认为短期 JWT 只会让它更安全。
【解决方案4】:

这里有很多好的答案。我将整合一些我认为最相关的答案并添加更多建议。

1) JWT 令牌验证是否应该仅限于验证令牌本身的签名,仅依靠服务器机密的完整性,还是伴随单独的验证机制?

不,因为与令牌机密泄露无关的原因。每次用户通过用户名和密码登录时,授权服务器都应存储生成的令牌或有关生成的令牌的元数据。将此元数据视为授权记录。给定的用户和应用程序对在任何给定时间都应该只有一个有效的令牌或授权。有用的元数据是与访问令牌关联的用户 ID、应用程序 ID 以及访问令牌的发布时间(允许撤销现有访问令牌并发布新的访问令牌)。在每个 API 请求上,验证令牌是否包含正确的元数据。您需要保留有关何时发布每个访问令牌的信息,以便用户可以在其帐户凭据遭到破坏时撤销现有的访问令牌,然后重新登录并开始使用新的访问令牌。这将使用颁发访问令牌的时间(创建的授权时间)更新数据库。在每个 API 请求上,检查访问令牌的发布时间是否在创建的授权时间之后。

其他安全措施包括不记录 JWT 并要求使用 SHA256 等安全签名算法。

2) 如果 JWT 签名验证是验证令牌的唯一手段,这意味着服务器机密的完整性是断点,那么服务器机密应该如何管理?

服务器机密的泄露将允许攻击者为任何用户颁发访问令牌,并且在步骤 1 中存储访问令牌数据不一定会阻止服务器接受这些访问令牌。例如,假设已向用户颁发访问令牌,然后攻击者为该用户生成访问令牌。访问令牌的授权时间将是有效的。

就像 Akshay Dhalwala 所说,如果您的服务器端机密被泄露,那么您需要处理更大的问题,因为这意味着攻击者已经破坏了您的内部网络、源代码存储库或两者兼而有之。

但是,为了减轻受损服务器机密的损害并避免将机密存储在源代码中的系统涉及使用协调服务(如 https://zookeeper.apache.org)进行令牌机密轮换。每隔几个小时左右使用 cron 作业生成一个应用程序机密(无论您的访问令牌有效期多长),并将更新的机密推送到 Zookeeper。在每个需要知道令牌秘密的应用服务器中,配置一个 ZK 客户端,该客户端在 ZK 节点值更改时更新。存储一个primary和一个secondary secret,每次更改token secret时,将新的token secret设置为primary,将旧的token secret设置为secondary。这样,现有的有效令牌仍然有效,因为它们将根据次要秘密进行验证。当次要秘密被旧的主要秘密替换时,所有使用次要秘密发布的访问令牌无论如何都会过期。

【讨论】:

    【解决方案5】:

    IETF 在 oAuth 工作组中有一个 RFC 正在进行中,请参阅:https://tools.ietf.org/id/draft-ietf-oauth-jwt-bcp-05.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-24
      • 1970-01-01
      • 1970-01-01
      • 2013-02-25
      • 2010-12-21
      • 2019-03-23
      • 1970-01-01
      • 2018-03-06
      相关资源
      最近更新 更多