Best practices for server-side handling of JWT tokens

(spawned from this thread since this is really a question of its own and not specific to NodeJS etc)

I'm implementing a REST API server with authentication, and I have successfully implemented JWT token handling so that a user can login through a /login endpoint with username/password, upon which a JWT token is generated from a server secret and returned to the client. The token is then passed from the client to the server in each authenticated API request, upon which the server secret is used to verify the token.

However, I am trying to understand the best practices for exactly how and to what extent the token should be validated, to make a truly secure system. Exactly what should be involved in "validating" the token? Is it enough that the signature can be verified using the server-secret, or should I also cross-check the token and/or token payload against some data stored in the server?

A token based authentication system will only be as safe as passing username/password in each request provided that it's equally or more difficult to obtain a token than to obtain a user's password. However, in the examples I've seen, the only information required to produce a token is the username and the server-side secret. Doesn't this mean that assuming for a minute that a malicious user gains knowledge of the server secret, he can now produce tokens on behalf of any user, thereby having access not only to one given user as would be the fact if a password was obtained, but in fact to all user accounts?

This brings me to the questions:

1) Should JWT token validation be limited to verifying the signature of the token itself, relying on the integrity of the server secret alone, or accompanied by a separate validation mechanism?

  • In some cases I've seen the combined use of tokens and server sessions where upon successful login through the /login endpoint a session is established. API requests validate the token, and also compare the decoded data found in the token with some data stored in the session. However, using sessions means using cookies, and in some sense it defeats the purpose of using a token based approach. It also may cause problems for certain clients.

  • One could imagine the server keeping all tokens currently in use in a memcache or similar, to ensure that even if the server secret is compromised so that an attacker may produce "valid" tokens, only the exact tokens that were generated through the /login endpoint would be accepted. Is this reasonable or just redundant/overkill?

2) If JWT signature verification is the only means of validating tokens, meaning the integrity of the server secret is the breaking point, how should server secrets be managed? Read from an environment variable and created (randomized?) once per deployed stack? Re-newed or rotated periodically (and if so, how to handle existing valid tokens that were created before rotation but needs to be validated after rotation, perhaps it's enough if the server holds on to the current and the previous secret at any given time)? Something else?

Maybe I'm simply being overly paranoid when it comes to the risk of the server secret being compromised, which is of course a more general problem that needs to be addressed in all cryptographic situations...

48640 次浏览

我也一直在为我的应用程序玩代币。虽然无论如何我都不是专家,但我可以分享一些我在这个问题上的经验和想法。

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

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

回答你的问题:

1)根据我有限的经验,最好使用第二个系统来验证令牌。简单地验证签名只意味着令牌是用您的秘密生成的。将任何已创建的令牌存储在某种 DB (redis、 memcache/sql/mongo 或其他存储)中是确保只接受服务器已创建的令牌的极好方法。在这种情况下,即使您的秘密被泄露,也不会有太大的影响,因为任何生成的令牌无论如何都是无效的。这就是我对我的系统采取的方法-所有生成的令牌都存储在一个 DB (redis)中,在每次请求时,我在接受之前验证令牌是否在我的 DB 中。通过这种方式,可以以任何理由撤消令牌,比如以某种方式释放到野外的令牌、用户注销、密码更改、秘密更改等等。

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

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

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

    处理方法: 将生成的散列存储到会话存储器中,存储时间为过期时间。要验证令牌,它需要由服务器发出。

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

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

Some libraries validating JWT would accept this one without checking the hash. That means that without knowing your salt used to sign the token, a hacker could grant himself some rights. Always make sure this can't happen. https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/

c.: Using a cookie with a session Id would not be useful to validate your token. If someone wants to hijack the session of a lambda user, he would just have to use a sniffer (eg : wireshark). This hacker would have both information at the same time.

  • 2: It is the same for every secret. There is always a way to know it.

The way I handle it is linked to the point 1.a. : I have a secret mixed with a random variable. The secret is unique for every token.

However, I am trying to understand the best practices for exactly how and to what extent the token should be validated, to make a truly secure system.

If you want the best security possible, you should not blindly follow best practices. The best way is to understand what you're doing (I think it's ok when I see your question), and then evaluate the security you need. And if the Mossad want to have access to your confidential data, they 'll always find a way. (I like this blog post : https://www.schneier.com/blog/archives/2015/08/mickens_on_secu.html )

在应用程序中实现 JWT 时,需要考虑以下几点:

  • Keep your JWT lifetime relatively short, and have it's lifetime managed at the server. If you don't, and later on need to require more information in your JWTs, you'll have to either support 2 versions, or wait until your older JWTs have expired before you can implement your change. You can easily manage it on the server if you only look at the iat field in the jwt, and ignore the exp field.

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

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

  • 考虑实现同时支持以下两点的 JWT,而不是简单地在一定时间后终止 JWT:

    • 使用次数-只能在过期前使用 N 次
    • 在一定的时间后过期(如果你有一个只能使用一次的令牌,你不希望它永远活着,如果不使用,你呢?)
  • 所有 JWT 身份验证失败都应该生成一个“ error”响应头,说明 JWT 身份验证失败的原因。例如「过期」、「没有使用权」、「撤销」等。这有助于实现者了解 JWT 失败的原因。

  • 考虑忽略您的 JWT 的“头部”,因为它们会泄露信息,并将一定程度的控制权交给黑客。这主要关系到头部中的 alg字段——忽略这一点,只是假设头部是您想要支持的,因为这样可以避免黑客试图使用 None算法,从而删除签名安全检查。

  • JWT 应该包含一个标识符,详细说明哪个应用程序生成了令牌。例如,如果你的 JWT 是由两个不同的客户端,mychat 和 myclassfiedsapp 创建的,那么每个都应该在 JWT 的“ iss”字段中包含它的项目名或类似的东西,例如“ iss”: “ mychat”

  • JWT 的日志不应该记录在日志文件中。可以记录 JWT 的内容,但不能记录 JWT 本身。这样可以确保开发人员或其他人无法从日志文件中获取 JWT 并对其他用户帐户执行操作。
  • 确保您的 JWT 实现不允许使用“ None”算法,以避免黑客在不签名令牌的情况下创建令牌。通过忽略 JWT 的“头部”,可以完全避免这类错误。
  • 强烈地考虑在您的 JWT 中使用 iat(在)而不是 exp(到期)。为什么?因为 iat基本上意味着 JWT 是何时创建的,所以当 JWT 到期时,您可以根据创建日期在服务器上进行调整。如果有人在20年后传入一个 exp,那么 JWT 基本上将永远存在!请注意,如果 JWT 的 iat在将来会过期,那么它们将自动过期,但是如果客户机的时间与服务器的时间稍有不同步,那么将留出一些回旋余地(例如10秒)。
  • 考虑实现一个端点,用于从 json 有效负载创建 JWT,并强制所有实现客户端使用该端点来创建它们的 JWT。这可以确保您可以通过如何在一个地方轻松地创建 JWT 来解决您想要解决的任何安全问题。我们在应用程序中没有直接这样做,现在必须慢慢地进行 JWT 服务器端安全更新,因为我们的5个不同的客户端需要时间来实现。另外,让创建端点接受 JWT 要创建的 json 有效负载数组,这将减少为客户端进入此端点的 http 请求的数量。
  • If your JWT's will be used at endpoints that also support use by session, ensure you don't put anything in your JWT that's required to satisfy the request. You can easily do this if you ensure your endpoint works with a session, when no JWT is supplied.
  • So JWT's generally speaking end up containing a userId or groupId of some sort, and allow access to part of your system based on this information. Make sure you're not allowing users in one area of your app to impersonate other users, especially if this provides access to sensitive data. Why? Well even if your JWT generation process is only accessible to "internal" services, devs or other internal teams could generate JWTs to access data for any user, e.g. the CEO of some random client's company. For example, if your app provides access to financial records for clients, then by generating a JWT, a dev could grab the financial records of any company at all! And if a hacker gets into your internal network in anyway, they could do the same.
  • 如果您打算允许以任何方式缓存包含 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 不能从一个用户的浏览器传递到另一个用户的浏览器。

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

1) Should JWT token validation be limited to verifying the signature of the token itself, relying on the integrity of the server secret alone, or accompanied by a separate validation mechanism?

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

其他安全措施包括不记录 JWT 和需要安全签名算法(如 SHA256)。

2) If JWT signature verification is the only means of validating tokens, meaning the integrity of the server secret is the breaking point, how should server secrets be managed?

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

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

However, a system to mitigate the damage of a compromised server secret and avoid storing secrets in source code involves token secret rotation using a coordination service like https://zookeeper.apache.org. Use a cron job to generate an app secret every few hours or so (however long your access tokens are valid for), and push the updated secret to Zookeeper. In each application server that needs to know the token secret, configure a ZK client that is updated whenever the ZK node value changes. Store a primary and a secondary secret, and each time the token secret is changed, set the new token secret to the primary and the old token secret to the secondary. That way, existing valid tokens will still be valid because they will be validated against the secondary secret. By the time the secondary secret is replaced with the old primary secret, all of the access tokens issued with the secondary secret would be expired anyways.

IETF have a RFC in progress in the oAuth Working Group see : https://tools.ietf.org/id/draft-ietf-oauth-jwt-bcp-05.html