JSON Web 令牌 (JWT) 作为用户标识和身份验证令牌

信息安全 验证 休息
2021-08-30 07:40:14

我正在实现一个需要身份验证的 REST 服务。我无法存储任何每个用户的状态(例如随机生成的令牌),因为我的服务无法直接访问数据库,只能访问另一个后端服务。

我想出的解决方案是在用户进行身份验证时创建一个 JSON Web Token ( JWT )。JWT 声明集在主题(“子”)字段中包含用户 ID。然后,服务器使用 AES GCM 和 256 位密钥(“enc”:“A256GCM”)直接加密声明集(“alg”:“dir”),创建JWE密钥在服务启动时生成一次并存储在内存中。

为了进行身份验证,客户端提交用户名/密码,服务器使用上述令牌进行响应。然后客户端随每个后续请求发送该令牌。

当客户端通过后续请求提交令牌时,服务器使用密钥对其进行解密,并假定“子”字段中的用户 ID 为当前用户的 ID,无需任何进一步的身份验证检查。令牌过期由 JWT 声明集中的“exp”字段处理。

客户端和服务器之间的连接将使用 SSL/TLS,因此令牌不会泄漏。

我正在使用这个库来创建和读取 JWT,因为我不相信自己会编写正确的加密代码。

我的问题:

  1. 上述方法安全吗?攻击者可以通过操纵令牌来冒充另一个用户吗?
  2. 方法是否过于复杂?使用 MAC(换句话说:JWS)而不是加密是否具有相同的安全性?(或者可能更多,因为它更简单并且出错的可能性更小)。JWT 声明集中没有什么特别秘密的,用户知道自己的 ID 并不重要。
  3. 我选择的 JWE 算法和加密是否合适?
    • 对于 JWE “alg”,我使用的库支持直接加密(直接使用密钥加密声明集)和 RSA(生成新密钥来加密每个令牌的声明集,并使用生成的密钥加密RSA 公钥)。我选择前者是因为生成对称密钥比生成 RSA 密钥更容易。
    • 对于 JWE “enc”,该库支持 AES GCM 和 AES CBC HMAC SHA2(具有各种位长)。我随意选择了GCM。
2个回答

您的基本方法是有效的:在用户登录时生成 JWT,期望后续消息携带 JWT,如果 JWT 有效,则在后续消息中信任 JWT 中的主题字段。但是有几件事你应该注意:

  1. 正如大雪所说,您可以使用 MAC(“alg”:“HS256”),因为 MAC 专门设计用于防止更改有效负载,而加密算法通常(违反直觉)不是。但是,由于您专门在GCM模式下使用 AES,因此您已经获得了防篡改加密(“经过身份验证的加密”),所以这并不是真正的问题。
  2. 在验证传入的 JWT 时,请注意您认为有效的内容。例如,我可以使用 {"sub":"me","alg":"none"} 调用您的服务,虽然 JWT 在某种意义上是有效的,但您不想接受它。
  3. 由于 JWT 是一个草案,还不是标准,它可能会改变。如果它发生了足够的变化,那么您正在使用的库可能必须以破坏与您的代码兼容性的方式进行更改。
  4. 如果您无法存储任何服务器端状态,则您无法在用户注销时使 JWT 失效。实际上,您的服务没有注销功能,这可能是一个安全问题,尤其是如果您将过期时间设置得太远。
  5. 如果您将过期时间设置得太快,您可能会遇到用户仍在登录但没有有效 JWT 的问题。这可能会导致尴尬的错误处理和用户工作流程问题。

既然您说您的服务器无法访问数据库,我假设实际登录是在其他地方处理的,也许是您提到的后端服务器。你没有说你的服务器是如何知道用户刚刚登录的。根据用户对你的服务和他们知道他们登录的东西之间关系的看法,上面的最后两点可能没有实际意义。

如果没有任何数据是敏感的,那么您需要做的就是保持数据的完整性。签署(JWS)令牌应该足以做到这一点。

如果您只是在做一个签名,那么您应该可以使用 HMAC SHA-256。请记住设置令牌的到期时间,并检查用户是否手动注销(在这种情况下,使令牌无效)。一旦您考虑到到期和注销,就不必担心太多(算法方面)。某人在单个会话期间(应该)破解 SHA-256 的机会相对较低(假设您需要以合理的时间间隔重新验证)。

与签名一样,请确保您提供要签名的内容(用户名、帐户类型等)。永远不要让任何用户定义的数据被签名,否则您可能会处于危险境地。

免责声明:这篇文章完全是我的意见。我没有对我的答案的可靠性或适用性做出任何声明。您应该始终咨询安全专家来讨论您的具体安全实施问题。这纯粹是教育性的。