JWT被盗了怎么办?

290

我正试图为我的RESTful API实现基于JWT的无状态认证。

据我所知,JWT基本上是一个加密字符串,在REST调用期间作为HTTP头部传递。

但如果有人窃听请求并窃取令牌怎么办?那么他将能够冒充我的身份进行请求吗?

实际上,这个问题适用于所有基于令牌的身份验证

如何防止这种情况?使用像HTTPS这样的安全通道?


3
因此,令牌通常只有短暂有效期。如果您关心数据的保密性,应该使用HTTPS。 - Jonathon Reinhart
6
但如果令牌即将过期,我的客户端将不得不不时通过重新进行身份验证来获取新的令牌。这不是有点繁琐吗? - smwikipedia
@JonathonReinhart 我想我明白了令牌为什么是短暂的。因为这样,服务器就不需要跟踪令牌的过期时间,从而为可扩展性留下空间。这是在“更好地控制令牌过期”和“实现更好的可扩展性”之间做出的一种“权衡”。 - smwikipedia
根据这里的内容(https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/),似乎需要使用HTTPS。 - smwikipedia
4
这也有帮助吗?一种常见的安全机制是跟踪请求的 IP 地址来源,以便检测令牌盗窃。在此页面的最后一节中详细描述了此机制。链接:https://firebase.google.com/docs/auth/admin/manage-sessions - Ula
4
理论上,防止令牌盗窃是不可能的。我们能做到的最好方法就是检测到它发生后尽快撤销会话。最佳的检测方法是使用轮换刷新令牌(正如RFC 6819所建议的那样)。这里有一篇博客详细解释了这个过程:https://supertokens.io/blog/the-best-way-to-securely-manage-user-sessions - Rishabh Poddar
8个回答

387

我是一个处理身份验证的Node库express-stormpath的作者,所以我会在这里提供一些信息。

首先,JWT通常不会被加密。虽然有一种方法可以加密JWT(参见:JWEs),但出于许多原因,这在实践中并不常见。

接下来,任何形式的身份验证(使用JWT还是不使用)都容易受到中间人攻击。当攻击者可以查看您通过互联网进行请求时的网络流量时,就会发生此类攻击。这就是你的ISP、NSA等所能看到的。

这就是SSL帮助防止的:通过从计算机加密您的网络流量->在认证时与某个服务器,第三方监视您的网络流量的人无法看到您的令牌、密码或任何其他东西,除非他们以某种方式能够获得服务器的私钥(不太可能)。这就是所有形式的身份验证都要求使用SSL的原因。

假设有人能够利用您的SSL并能够查看您的令牌:您的答案是,是的,攻击者将能够使用该令牌来冒充您并向您的服务器发出请求。

现在,这就是协议发挥作用的地方。

JWT只是身份验证令牌的一个标准。它们可以用于几乎任何事情。 JWT之所以有点酷,是因为您可以嵌入额外的信息,并且可以验证没有人对其进行了更改(签名)。

然而,JWT本身与“安全”无关。基本上,JWT与API密钥差不多:只是您用来验证某个服务器的随机字符串。

让您的问题更有趣的是正在使用的协议(很可能是OAuth2)。

OAuth2的工作方式是,它旨在为客户端提供临时令牌(例如JWT)进行身份验证,仅在短时间内使用!

这样设计的想法是,如果您的令牌被盗取,攻击者只能在短时间内使用它。

使用OAuth2,您必须定期重新通过提供用户名/密码或API凭据来进行身份验证,然后交换令牌。

由于此过程会不时发生,因此您的令牌将经常更改,使攻击者难以在不经过大量麻烦的情况下不断冒充您。

希望这有所帮助^^


5
以下文章的作者认为JWT的一个劣势是,唯一从被盗用的JWT中恢复的方法是生成新的密钥对并有效地注销所有用户。而使用存储在数据库中的会话ID,则可以仅删除受影响用户的会话,并使其在所有设备上注销。我不确定OAuth2在这里的作用,或者它是否有助于减轻所呈现的缺点。 - Marcel
8
作者是错误的。您可以使用不同的设计模式来使令牌失效。但总的来说,将JWT用于任何身份验证目的都是一个坏主意。使用带有嵌入式会话ID的会话cookie并进行密码签名要更有效率。 - rdegges
1
@rdegges请告诉我JWT在身份验证方面为什么是个坏主意?以及我如何使用您在上面评论中提到的会话cookie? - noman tufail
14
这段内容太长了,无法在单个回复中输入。如果你想了解更多,我已经对此议题进行了详细的讲解。你可以在线查看我的幻灯片:https://speakerdeck.com/rdegges/jwts-suck-and-are-stupid - rdegges
2
从理论上讲,防止令牌盗窃是不可能的。我们能做的最好的事情就是检测到发生了这种情况,然后尽快撤销会话。最佳的检测方法是使用旋转刷新令牌(如RFC 6819所建议的)。这里有一篇博客详细解释了这个问题:https://supertokens.io/blog/the-best-way-to-securely-manage-user-sessions - Rishabh Poddar
显示剩余3条评论

39

我知道这个问题很老了,但我想分享我的看法,也许有人可以改进或提供一个理由来完全拒绝我的方法。我在一个基于RESTful API的HTTPS中使用JWT。

为了使它工作,你应该始终发行短期的令牌(取决于大多数情况,在我的应用程序中,我实际上将exp声明设置为30分钟,并将ttl设置为3天,因此只要令牌的ttl仍然有效并且令牌未被列入黑名单,就可以刷新此令牌)。

对于认证服务,为了使令牌无效,我喜欢使用内存缓存层(在我的情况下是Redis)作为JWT黑名单/禁止列表,具体取决于一些标准: (我知道这会破坏RESTful哲学,但存储的文档真的很短暂,因为我为其剩余的生存时间-ttl声明-列入黑名单)

注意:列入黑名单的令牌不能自动刷新

  • 如果更新了user.passworduser.email(需要密码确认),认证服务会返回一个新的令牌并使先前的令牌无效(列入黑名单)。因此,如果你的客户端检测到用户的身份已经被某种方式破坏,你可以要求该用户更改其密码。如果你不想使用黑名单,你可以(但我不鼓励你)将iat(发行时间)声明与user.updated_at字段验证(如果jwt.iat < user.updated_at,那么JWT就无效)。
  • 用户主动注销。

最后,你可以像其他人一样正常验证令牌。

注意2: 不建议将令牌本身作为缓存的键(令牌非常长)。我建议生成并使用UUID令牌来代替,并将其用于jti声明。这很好,我认为你可以使用相同的UUID作为CSRF令牌,通过返回一个带有secure/non-http-only的cookie以及使用js正确实现X-XSRF-TOKEN头。这样,您就避免了创建另一个令牌进行CSRF检查的计算工作。


6
如果您需要在服务器上存储一个黑名单,并且需要针对每个请求进行检查,为什么不直接使用普通的会话(session)呢? - Franklin Yu
@FranklinYu 黑名单比完整的会话存储要“便宜”得多。由于您正在存储短寿命的键值对象(取决于其剩余的生存时间,应该很短),并且仅在注销操作和使令牌无效的操作中发生,因此并非每个令牌都被存储。 - Frondor
10
它能有多便宜?首先,如果您仍在服务器端存储任何内容,则无法享受JWT声明的“可扩展性”好处,因为仍然存在一个中央黑名单服务器,所有应用程序服务器在执行任何操作之前都需要与其通信。如果由于快速过期而只需要存储1k个黑名单,则会话也可以采用相同的方法,并且只需要存储1k个会话。 - Franklin Yu
3
我喜欢这种方法。实际上,并不需要在每个请求中都检查黑名单,只需在JWT过期后的请求(可以从令牌本身读取)和TTL期限内的请求中进行检查。在“标准”使用情况下,一个令牌的生命周期中最多只会发生一次。刷新后,您可能可以拒绝任何未来的刷新请求。感谢@Frondor。 - John Ackerman

12

抱歉稍微晚了点,但我有类似的担忧,现在想对此做出一些贡献。

1) rdegges提出了一个很好的观点,即JWT与“安全”无关,仅验证是否有人搞乱了有效载荷或未签名;ssl可以帮助防止违规行为。

2) 现在,如果ssl也被某种方式破坏,那么任何窃听者都可以窃取我们的bearer token(JWT),并冒充真实用户,接下来可以采取的下一步是从客户端请求"proof of possession"(拥有证明)的JWT。

3) 现在,采用这种方法,JWT的呈现者拥有特定的Proof-Of-Possession(POP)密钥,接收方可以通过加密确认请求是否来自同一真实用户。

我参考了Proof of Possesion这篇文章,并对这种方法深信不疑。

如果能够做出任何贡献,我将非常高兴。

干杯 (y)


10
为了解决令牌被盗的问题,您可以将每个JWT与有效IP列表进行映射。
例如,当用户使用特定IP登录时,您可以将该IP添加为该JWT的有效IP,当您从另一个IP获取此JWT的请求(无论是用户更改了互联网还是JWT被盗,或任何原因),您可以根据您的用例执行以下操作:
  1. 将CSRF令牌与用户令牌进行映射,如果它被盗,则其CSRF令牌将不匹配,您可以使该用户令牌无效。
  2. 您可以向用户提供验证码以验证他是否是有效用户。如果他输入验证码,则将该IP添加到该JWT的有效列表中。
  3. 您可以注销用户并发出新的登录请求。
  4. 您可以警告用户您的IP已更改或来自不同位置的请求。
在以上用例中,您也可以使用缓存并设置较小的5分钟到期时间,而不是每次都进行检查。
请建议如何改进。

你的直觉很好,但搜索一下“令牌绑定”,这将JWT绑定到TLS会话。 - TrophyGeek
1
不是一个高效的方法。我的家庭WiFi的公共IP几乎每天都会更改。这意味着,我每天都必须应用三个提到的点之一。 - The Coder
@Puspender 这完全取决于使用情况,您想要保持多安全,例如银行网站通常使用此方法,我猜Discord在每次登录时都使用此方法。 - aRvi

3

我们能否将请求生成此JWT令牌的初始主机的IP地址作为声明的一部分添加到令牌中?现在,当JWT被窃取并从不同的机器上使用时,当服务器验证此令牌时,我们可以验证请求的机器IP地址是否与作为声明的一部分设置的IP地址匹配。这将不匹配,因此令牌可以被拒绝。另外,如果用户尝试通过将自己的IP地址设置为令牌来操纵令牌,则令牌将被拒绝,因为令牌已被更改。


3
这是一个可能的解决方案,但对于位于防火墙后面的客户端来说,从地址池中选择一个 IP 地址并且随时都可能会变动。 - SpeedOfSpin
3
如果存在转发代理,或者用户每次连接时都使用VPN,那么这种方法就不值得尝试了。 - Ebrahim ElSayed
3
移动用户也是一个问题。我的移动IP每天会变多次。 - liquidki
1
仅适用于固定IP地址的设备。它无法防止ColleagueA从ColleagueB那里窃取JWT(两位同事都在同一个公共IP地址后面的办公网络中工作)。 - Lloyd
在我的情况下,API的客户端是移动电话,它们的IP地址变化很快,因为它们可能在Wi-Fi和4G上。这是不可行的。 - undefined

1
一旦令牌被盗,游戏就结束了。然而,有一种方法可以使盗用令牌变得更加困难。
请参考https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html#token-sidejacking
基本上,您需要创建一个x字节长的十六进制指纹,并将其原始值存储在令牌中-使用例如SHA-512对指纹进行哈希处理,并将已哈希的指纹放置在httponly安全cookie中。
现在,您需要验证令牌的签名和过期日期以及验证cookie的存在,并确保原始指纹值匹配。

0
客户端应该使用用户密码哈希的一部分来加密客户端发送到服务器的http消息的时间。当创建令牌时,这个哈希的一部分也应该用一些服务器秘钥进行加密。
然后服务器可以解密HTTP请求时间并验证短时间延迟。
令牌将在每个请求中更改。

0
为什么不使用一次性密码(OTP)呢? 是的,令牌被盗了,有人在冒充我。但是对于关键操作,比如涉及付款或更新我的个人资料,为什么不使用一次性密码呢? 显然,这取决于您在使用API时做什么,比如Facebook应用程序,当有人阅读信息时,您可能不太在意,但是当他们更改您的账户密码或从群组中删除某人时,需要使用一次性密码。对于银行应用程序,每次登录和每次付款都需要使用一次性密码。
这只是我的个人意见。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接