如果您能解码JWT,那么它们如何保密安全?

584
如果我得到一个JWT并且我可以解码有效负载,那么这是如何保证安全的?我不能只是从标头中获取令牌,解码并更改有效负载中的用户信息,然后使用相同的正确编码的密钥将其发送回去吗?
我知道它们必须是安全的,但我真的很想了解这些技术。我错过了什么?

14
md5('original messaged' + secret) != md5('changed message' + secret),因此如果有人更改了消息,您可以检测到它。 - Pithikos
1
@YashKumarVerma 是的,这只是为了演示其要点,因为每个人都知道md5。 - Pithikos
4
@user1955934,这是Base64编码,不是加密。你可以使用任何Base64解码器简单地进行解码。 - Pithikos
1
客户端需要发送哈希和JWT令牌吗?然后在服务器端,他们将尝试使用密钥对JWT令牌进行哈希处理并与哈希值进行比较? - user1955934
1
服务器不应该信任客户端来处理和返回敏感数据。只有哈希值,服务器才能在内部使用它来检索实际数据,很可能是从数据库中获取的。 - Petruza
显示剩余3条评论
9个回答

666

JWT可以签名、加密或同时使用。如果一个令牌只被签名,未被加密,所有人都能读取其内容,但是当你不知道私钥时,你无法更改它。否则,接收方会注意到签名不再匹配。

回答您的评论:我不确定我是否正确理解了您的评论。为了确保:您是否了解数字签名?我将简要介绍一种变体(HMAC,它是对称的,但还有许多其他变体)。

假设Alice想向Bob发送JWT。他们都知道一些共享秘密。Mallory不知道那个秘密,但想干扰并更改JWT。为了防止这种情况发生,Alice计算 Hash(payload + secret) 并将其作为签名附加在后面。

接收消息时,Bob也可以计算 Hash(payload + secret) 来检查签名是否匹配。 然而,如果Mallory更改了内容,她就不能计算匹配的签名(它应该是Hash(newContent + secret))。她不知道秘密,也没有办法找到它。这意味着如果她更改了什么,签名就不再匹配了,Bob将不再接受JWT。

假设我向另一个人发送消息{"id":1}并用Hash(content + secret)进行签名。 (+在这里只是连接)。我使用SHA256哈希函数,得到的签名为:330e7b0775561c6e95797d4dd306a150046e239986f0a1373230fda0235bda8c。现在轮到你了:扮演Mallory的角色,尝试对消息{"id":2} 进行签名。你不能因为你不知道我使用了哪个秘密。如果我假设接收方知道秘密,他可以计算任何消息的签名并检查是否正确。


14
当负载发生更改时,签名也会更改吗?我原本认为令牌的格式是[头].[负载].[签名]签名是否通过组合负载和密钥来计算?如果是这样的话,一个具有不同id的负载对于该密钥来说不是相同的吗?比如数据是{ id:1 },并且使用该密钥计算令牌的签名部分,那么这是否意味着{ id:2 }将对用户2有效,因此用户1可以将id更改为2,而令牌仍然是有效的? - Z2VvZ3Vp
17
哦,现在我明白了。我不知道为什么我忽略了这个想法——当你改变有效载荷时,秘密哈希值将不正确,因为必须重新计算秘密哈希值。出于某种原因,我仍然认为它是独立的。最后一部分真的让我明白了。谢谢你的引导。 - Z2VvZ3Vp
56
我有一个相关的问题。如果有人使用复制的JWT来冒充Alice,有什么防止措施吗? - Morrowless
46
如果有人拥有JWT,他们可以冒充Alice。因此,您需要小心存储和发送它。您还应该在有效载荷中设置其过期时间。这样,如果有人窃取了JWT,他们只能在有限的时间内使用它。请查看https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage。 - Geraint Anderson
5
@Morrowless,我遇到了用户“分享”令牌的问题。为了提高安全性,我们在解密过程中使用了用户的IP地址。由于这是在后台进行的,一个用户可以与另一个用户分享令牌,但因不知道原因被拒绝。同时,我们还销毁/使共享的令牌失效,以便两个用户都必须重新登录并获取新的令牌。当然,这并不完美,因为如果您连接到同一网络,仍然可以逃脱惩罚。我希望看到的是浏览器通过标头传递强制执行的会话ID。 - calcazar
显示剩余9条评论

278
让我们从最基础的开始讨论:JWT是一种非常现代、简单和安全的方法,它扩展了Json Web Tokens。Json Web Tokens是一种无状态的身份验证解决方案。因此,在服务器上不需要存储任何会话状态,这当然非常适合restful API。
Restful API应该始终是无状态的,而使用JWT进行身份验证的最广泛的替代方案是仅使用会话在服务器上存储用户的登录状态。但是这不遵循restful API应该是无状态的原则,这就是为什么像JWT这样的解决方案变得流行和有效的原因。
所以现在让我们了解一下Json Web Tokens的身份验证实际上是如何工作的。假设我们的数据库中已经有一个注册用户。那么用户的客户端首先通过用户名和密码发出POST请求,然后应用程序检查用户是否存在以及密码是否正确,然后应用程序将为该用户生成唯一的Json Web Token。
令牌是使用存储在服务器上的密钥字符串创建的。接下来,服务器将该JWT发送回客户端,客户端将其存储在cookie或本地存储中。 enter image description here 就像这样,用户被认证并基本上登录到我们的应用程序中,而不会在服务器上留下任何状态。
因此,服务器实际上不知道哪个用户实际上已登录,但是当然,用户知道他已登录,因为他有一个有效的Json Web Token,这有点像访问应用程序受保护部分的护照。
所以再次确保您理解了这个想法。只要用户获得他的唯一有效的Json Web Token,他就登录了,而该令牌不会在服务器上保存任何地方。因此,这个过程完全是无状态的。
然后,每当用户想访问受保护的路由,例如他的用户配置文件数据时,他都会发送他的Json Web Token和请求,因此这有点像展示他的护照以获得访问那个路由的权限。
一旦请求到达服务器,我们的应用程序将验证Json Web Token是否有效以及用户是否真的是他所说的那个人,然后请求的数据将发送到客户端,如果不是,则会出现错误提示用户他无权访问该资源。 enter image description here 所有这些通信必须通过https进行,以便安全加密Http,以防止任何人可以获取密码或Json Web Tokens。只有这样我们才有一个真正安全的系统。

enter image description here

所以,一个Json Web Token(JWT)看起来像是这个屏幕截图的左边部分,该截图是从jwt.io的JWT调试器中获取的。实际上,它是由三个部分组成的编码字符串,即头部、有效载荷和签名。头部只是有关令牌本身的一些元数据,有效载荷是我们可以将任何数据编码到令牌中的数据,任何数据都可以。因此,在此处编码的数据越多,JWT就越大。无论如何,这两个部分只是将被编码但不加密的纯文本。 因此,任何人都能够解码并阅读它们,我们不能在这里存储任何敏感数据。但这根本不是问题,因为在第三部分中,也就是签名中,事情真正有趣。签名是使用头部、有效载荷和保存在服务器上的密钥创建的。
然后整个过程被称为签署Json Web Token。签名算法使用头部、有效载荷和密钥创建唯一的签名。因此,只有这些数据加上密钥才能创建此签名,对吗?然后,与头部和有效载荷一起,这些签名形成JWT,然后发送给客户端。 enter image description here 一旦服务器接收到JWT以授予对受保护路由的访问权限,它需要验证它以确定用户是否真的是他声称的人。换句话说,它将验证令牌的头部和有效载荷数据是否已更改。因此,此验证步骤将检查是否有第三方实际上更改了Json Web Token的头部或有效载荷。
那么,这个验证实际上是如何工作的呢?嗯,实际上非常简单。一旦收到JWT,验证将获取其头部和有效载荷,并与仍保存在服务器上的密钥一起创建一个测试签名。
但是,最初创建JWT时生成的原始签名仍然在令牌中,对吧?这就是验证的关键。因为现在我们只需要将测试签名与原始签名进行比较即可。
如果测试签名与原始签名相同,则意味着有效载荷和头部未被修改。 enter image description here 因为如果它们被修改了,那么测试签名就必须不同。因此,在这种情况下,如果数据没有被修改,我们就可以验证用户。当然,如果两个签名实际上是不同的,那么就意味着有人篡改了数据。通常是尝试更改有效载荷的第三方进行操作。但是,操纵有效载荷的第三方当然无法访问密钥,因此他们无法签署JWT。因此,原始签名永远不会对应于被篡改的数据。因此,在这种情况下,验证将始终失败。这是使整个系统运作的关键。这是使JWT如此简单但也极其强大的魔力所在。

4
嗨@Lord,这太棒了!我不知道为什么它的赞数这么少! - ani0904071
8
这个回答很好!简而言之,如果JWT是“2.5.9”(头部=2,载荷=5,签名=9),那么篡改并将数据更改为6将不起作用。假设服务器的密钥是将头部载荷乘以并减去1。 "测试签名" 将会是 26-1=11。但是JWT的签名是9,所以服务器知道有东西被篡改了。一旦理解了,就很容易,但出于某种原因,许多文章省略了低级别的内容,这使得它看起来比实际上更神奇和复杂。 - Magnus
2
谢谢,现在我明白了。如此简单,却又难以理解。现在看起来微不足道。 - Jesus
6
问题不在于修改JWT时会发生什么,而在于当B可以访问A的JWT时会发生什么(例如他们相邻坐着,A去上厕所,B在A的chrome中打开开发工具并复制粘贴本地存储中的JWT并将其发送到自己的计算机),并使用它来调用API。如何确定从B的计算机发出的请求(实际上是针对A发出的)是否无效? - Thanasis Ioannidis
4
@ThanasisIoannidis 这让我想起了会话劫持,但有些不同,因为会话劫持的主要攻击方式是嗅探。现在,对于你的情况,B人仍然以某种方式“劫持”A人的JWT令牌。在嗅探案例中,可以通过使用HTTPS来解决这个问题,但在你的情况下,我们无能为力。实际上,这是一个信息安全问题,需要教育员工不要在离开桌子时不锁定他们的电脑。 - Matheus
显示剩余15条评论

252
你可以前往jwt.io,将你的令牌粘贴进去并阅读内容。这对许多人来说最初是令人不安的。
简短的回答是JWT不关心加密,而是关心验证。也就是说,它始终可以回答“此令牌的内容是否被篡改了”的问题?这意味着用户操纵JWT令牌是徒劳的,因为服务器将知道并忽略该令牌。服务器在向客户端发放令牌时基于有效负载添加签名。稍后,服务器将验证有效负载和匹配的签名。
逻辑上的问题是为什么不关心加密内容的动机是什么?
1. 最简单的原因是,它认为这在很大程度上已经解决了。例如,如果处理像Web浏览器这样的客户端,则可以将JWT令牌存储在一个 "secure" cookie 中(不通过HTTP传输,仅通过HTTPS传输),并且 "httpOnly"(无法被JavaScript读取),并使用加密通道(HTTPS)与服务器通信。一旦您知道服务器和客户端之间有一个安全的通道,您就可以安全地交换JWT或任何其他想要的内容。
2. 这使得事情变得简单。简单的实现使得采用更容易,但它还可以让每个层次都做自己擅长的事情(让HTTPS处理加密)。
3. JWT不适合存储敏感数据。一旦服务器收到JWT令牌并对其进行验证,它就可以在自己的数据库中查找用户ID以获取有关该用户的其他信息(例如权限、邮政地址等)。这使得JWT的大小小并避免了意外信息泄漏,因为每个人都知道不要将敏感数据保存在JWT中。
这与Cookie本身的工作方式并没有太大区别。Cookie通常包含未加密的有效负载。如果您正在使用HTTPS,则一切都很好。如果您没有,则建议加密敏感Cookie本身。不这样做将意味着可能发生中间人攻击-代理服务器或ISP读取Cookie,然后稍后重播它们,假装是您。出于类似的原因,JWT应始终通过像HTTPS这样的安全层交换。

23
注意!JWT 应始终通过像 HTTPS 这样的安全层进行交换。 - codemirror
1
但如果JWT只在HTTPS上安全,那为什么不直接发送有效载荷呢? POST -> 用户名,密码。它仍然加密了对吧? - GeekPeek
2
你刚刚读到了我心中所想,事实上,作为一名新手,当我发现jwt.io能够读取我的有效载荷时,我感到非常惊讶,那么签名究竟在做什么?现在,我最大的问题是,如果你的JWT被盗了会发生什么?! - da45
2
敏感信息如密码、信用卡详细信息等,必须不编码在JWT中。 - tbhaxor
@lowcrawler 服务器必须验证JWT的签名。只有在JWT使用服务器自己的私钥签名时,服务器才会授予权限。在这种情况下,由于服务器没有签署它们,它不会授予权限。 - anolan23
显示剩余5条评论

34

JSON Web Token(JWT)中的内容本质上并不安全,但是有一个内置功能来验证令牌的真实性。JWT由三个哈希值组成,用句点分隔。第三部分是签名。在公钥/私钥系统中,发布者使用私钥对令牌签名,只有相应的公钥才能进行验证。

重要的是要理解发布者和验证者之间的区别。令牌的接收方有责任进行验证。

在Web应用程序中安全地使用JWT有两个关键步骤:1)通过加密通道发送它们,2)立即在接收到令牌时验证签名。公钥加密的非对称特性使得JWT签名验证成为可能。公钥可以验证JWT是否被其相应的私钥签名。没有其他密钥组合可以进行此验证,从而防止冒充尝试。遵循这两个步骤,我们可以以数学确定性保证JWT的真实性。

更多阅读:公钥如何验证签名?


30

我将用一个例子来解释这个概念。

假设我向您借了10美元,然后我给了您一张带有我的签名的 IOU(即欠条)。当您或其他人把这张 IOU 带回给我时,我就会还钱,并检查签名以确保它是我的。

我无法确保您不会向任何人展示此 IOU 的内容,甚至将其交给第三方,我所关心的只是这张 IOU 是否由我签名,当有人向我出示这张 IOU 并要求付款时。

JWT 的工作方式与此类似,服务器只能确保接收到的令牌是由其自己颁发的。

您需要采取其他措施来保护其安全性,例如使用 HTTPS 进行传输加密,确保存储令牌的本地存储处于安全状态,并设置来源。


完美的比喻! - stevevar
使用HTTPS进行传输加密,确保存储令牌的本地存储是安全的,并设置来源。请详细说明如何实现? - Eugene Zalivadnyi

15

参考 - JWT 结构与安全性

需要注意的是,JWT 用于授权而非认证。 因此,只有在服务器通过指定凭据对您进行身份验证后,才会为您创建 JWT。一旦为所有未来与服务器的交互创建了 JWT,就可以使用 JWT。因此,JWT 告诉服务器该用户已经过身份验证,如果他具有该角色,则让他访问特定资源。
JWT 负载中的信息对每个人都可见。可能存在“中间人”攻击并更改 JWT 的内容。因此,我们不应在负载中传递任何敏感信息,如密码。如果希望使其更加安全,可以加密负载数据。如果负载被篡改,服务器将识别它。
因此,假设用户已通过身份验证并获得了 JWT。生成的 JWT 具有指定 管理员 角色的声明。此外,签名是使用以下方式生成的:

enter image description here

现在,此 JWT 已被篡改,假设角色已更改为 超级管理员
然后,当服务器接收此令牌时,它将再次使用密钥(仅服务器拥有)和负载生成签名。这将不匹配 JWT 中的签名。因此,服务器将知道 JWT 已被篡改。


+1强调JWT不是用于身份验证,而是用于授权。我不知道为什么这么多网站和人说它们用于身份验证。 JWT根本不能证明请求者的身份。我猜每个人都试图推断出一个请求者只有在通过身份验证之后才能获得JWT,但这并不一定是真的。 - Ungeheuer
只是好奇..@Ungeheuer 你为什么认为JWT不被视为身份验证令牌? 它具有包含用户信息的有效负载。头部有效负载密钥用于“验证”原始签名(最初由身份验证服务器生成),这意味着如果有效负载中有任何更改,则用户被认为不是原始用户,因此不是真实的。难道这不简化了身份验证过程吗?也就是说,使用令牌,我们可以在每个请求中对用户进行身份验证,而无需再次访问身份验证服务器,然后根据用户角色允许访问资源。 - mabiyan
https://cloud.google.com/api-gateway/docs/authenticating-users-jwt 解释了其他方式,与这个答案解释的不同。 - mabiyan

3
只有存在于您服务器上的JWT私钥才能解密加密的JWT。知道私钥的人将能够解密加密的JWT。
请将私钥隐藏在您服务器的安全位置,并且永远不要告诉任何人私钥。

9
JWT并不总是加密的。它们可以被签署、加密、先签署再加密或先加密再签署。 - csauve

2

我不是密码学专家,所以(希望)我的答案能帮到那些也不是专家的人。

在编程中,使用密码学有两种可能的方式:

  1. 签名/验证
  2. 加密/解密

当我们想要确保数据来自可信源时,我们使用签名。 当我们想要保护数据时,我们使用加密。

签名/验证使用非对称算法,即我们使用一个密钥(私钥)进行签名,数据接收者使用另一个密钥(公钥)进行验证。

对称算法使用相同的密钥来加密和解密数据。

加密可以使用对称算法和非对称算法进行。关于这个主题的相对简单的文章

上述是常识,以下是我的观点。

当JWT用于简单的客户端到服务器的身份验证时,不需要签名或非对称加密。JWT可以使用快速且超安全的AES进行加密。如果服务器能解密它,说明服务器就是加密它的那一方。

总结:未经加密的JWT不安全。在没有第三方参与的情况下,可以使用对称加密代替签名。


0
密码 JsonWebtoken
有效期 直到密码被更改或强制更改,例如90天后。 只有短暂的时间,通常为1-24小时。然后需要刷新JWT。
重置 用户需要手动重置密码。 如果“客户端”,例如您的手机拥有JWT,则可以自动刷新。用户看不到这一过程。
安全性 每当JWT被刷新时,后端都会参与其中。JWT随时可能被撤销。
更高的安全性 “客户端”,例如其IP地址或移动设备ID(很难伪造)可以编码到JWT中。任何对此的篡改都可以在后端轻松检测到。

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