[译]5个简单的步骤去理解 JSON Web Tokens(JWT)

1,032 阅读7分钟

图片描述

原文作者:Mikey Stecky-Efantis
原文地址:5 Easy Steps to Understanding JSON Web Tokens(JWT)
译者:命名最头疼

在本文中, 将解释JSON Web Tokens(JWT)的基本原理以及使用他们的原因。JWT 是确保你应用程序信任和安全的重要部分。JWT 允许以安全的方式来声明,例如用户数据。

为了解释JWT如何工作,让我们从一个抽象的定义开始。

一个 JSON Web Token(JWT)是一个 JSON 对象,在 RFC7519 中定义为表示两方之间的一组信息的安全方式。该令牌由标头,有效负载和签名组成。 简单来说,JWT 只是一个具有以下格式的字符串

  header.payload.signature

应该注意的是,双引号字符串实际上被视为有效的 JSON 对象。

下面将展示实际使用 JWT 的方式和原因,我们将使用一个简单的例子(如下图所示),这个例子中的实体是用户,应用服务器,和认证服务器。认证服务器将提供 JWT 给用户,通过 JWT,用户可以安全的和应用服务器间进行通讯。

图片描述
在这个例子中,用户第一次进入认证服务器并使用认证服务器登陆系统(例:在 Facebook 和 Google 中通过用户名和密码登陆,等)。认证服务器创建JWT并且发生给用户,当用户对应用程序进行 API 调用时,JWT将随着API一并传递。在此配置中,应用程序服务器将会进行认证配置,用于验证传入的JWT是否是由身份服务器创建的(稍后将详细解释验证过程)。因此,当用户使用带有JWT的API去发起调用请求时,该应用能够使用JWT去认证这个API是否来自被认证的用户。

现在,将更深入地研究JWT本身及其构建和认证的方式。

Step1. 创建令牌头

JWT 的头部包含有关如何计算 JWT 签名的信息,其标头是以下格式的 JSON 对象

{
  "typ": "JWT",
  "alg": "HS256"
}

在上述 JSON 中,"typ" 键值指定了 JWT 对象,"alg"键值指定使用哪种散列算法来创建 JWT 签名组件。在这个例子中,我们使用 HMAC-SHA256 算法(一种使用密钥的散列算法)来计算签名(在步骤 3 中更详细地讨论)。

Step2. 创建 PayLoad

PayLoad是存储在 JWT 里的内部数据(该数据也称为 JWT 的 “声明”)。在这个例子中,认证服务器创建一个JWT 用于存储用户信息,特别是用户ID。

{
  "userId": "b08f86af-35da-48f2-8fab-cef3904660bd"
}

在这个例子中,我们只将一个声明放入 payload 中,你也可以根据需要添加任意数量的声明。JWT关键信息(payload)有几种不同的标准声明,例如 "iss" 表示 issuer,"sub" 表示 subject还有 "exp" 表示expiration time。创建 JWT 时,这些字段非常有用,但是他们是可选的,想了解更多有关 JWT 标准字段的详细信息,请参阅 JWT 上的维基百科页面。

请记住,数据的大小会影响 JWT 的整体大小,通常这不是问题,但是,JWT 太大可能会对性能产生负面影响并导致延迟。

Step3. 创建签名

签名的计算方式通过以下的伪代码进行表述

  // signature algorithm
  data = base64urlEncode( header ) + “.” + base64urlEncode( payload )
  hashedData = hash( data, secret )
  signature = base64urlEncode( hashedData )

该算法的作用是通过 base64url 对步骤1和步骤2中创建的头和关键信息(payload)进行编码。然后通过点(.)来连接两个编码字符串,构成数据 data 。在 JWT 头部使用指定的散列算法对数据字符串使用密钥进行散列,并将生成的散列数据分配给 hashedData。然后对该散列数据进行 base64url 编码以产生 JWT 签名。

在该例子中,头部和关键信息(payload)都是 base64url 编码的

// header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
// payload
eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ

然后,在编码头和编码关键负载(payload)中周期的加入携带密钥的应用指定签名算法,于是,我们得到签名所需的散列数据。在该例子中,这意味着应用HS256算法,并将密钥设置为字符串 "secret",在数据字符串上获取 hashedDate 字符串,之后,通过 base64url 编码 hashedData 字符串,我们得到以下 JWT 签名

// signature
-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM

Step4. 将 JWT 的所有组件(3个)组合在一起

我们已经创建了所有的组件(3个),现在我们可以通过它们来创建 JWT了。请记住 JWT 的结构 header.payload.signature ,我们使用通过 base64url 编码的 header 和 payload,以及步骤 3 中签署的签名,只需要组合这些组件并通过句号(.)分隔它们。

// JWT Token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM

通过浏览器,在jwt.io上你可以尝试创建属于自己的 JWT。

回到这个例子,现在该认证服务器能够发送 JWT 给用户了。

JWT 如何保护我们的数据?

要理解使用 JWT 的目的,并而不是通过任何的方式手段去隐藏或者模糊数据。使用 JWT 的原因是为了证明发送的数据实际上是由真实的源创建的。

如上述步骤所示,JWT 内的数据经过编码和签名而不是加密的。编码数据的目的是转换数据的结构。一方面签名数据允许数据接收器验证数据源的真实性。因此,编码和签名数据不会保护数据。另一方面,加密的主要目的是保护数据并防止未经授权的访问。有关编码和加密之间差异的详细说明,以及有关散列如何工作的更多信息,请参阅此文章

由于 JWT 仅被签名和编码,并且由于 JWT 未加密,因此 JWT 不保证敏感数据的任何安全性。

Step5. JWT 验证

在第三个例子中,我们使用由 HS256 算法签名的 JWT,其中只有身份验证服务器和应用服务器知道密钥。当应用程序设置其身份认证的时候,应用服务器从身份验证服务器接收密钥。由于应用程序知道密钥,因此当用户对应用程序调用JWT 连接的 API 时,应用程序可以在 JWT 上执行与步骤 3 相同的签名算法。然后该应用程序能够验证自身通过哈希操作获得的签名与 JWT 本身得到的签名是否匹配(即,它与由认证服务器创建的 JWT 签名匹配)。如果签名匹配,这意味着 JWT 有效,表示 API 的调用是来自认证服务器的。除此之外,如果签名不匹配,则表示收到的 JWT 无效,这意味着你的应用程序正受到潜在的攻击。因此,通过验证 JWT ,应用程序在自身和用户之间添加了一层信任。

结论

我们了解了 JWT 是什么,如何创建和验证它们,以及如何使用它们来确保应用程序与其用户之间的信任关系。这是了解 JWT 的基础和起点。在确保应用程序中的信任和安全性难题中,JWT 只是其中之一。

应该注意的是,本文中描述的 JWT 认证设置使用的是对称密钥算法(HS256),你也可以以类似的方式设置 JWT 身份验证,除非你使用非对称算法(例如:RS256)这类算法的身份验证服务器具有密钥,并且应用程序服务器具有公钥。查看此 Stack Overflow 问题了解对称和非对称算法的差异性及其详细分类。

还应该注意,JWT 应该通过 HTTPS(而不是 HTTP)连接。HTTPS 可以有效的防止未经授权的用户通过拦截服务器和用户之间通讯的方式来发送 JWT。

此外,如果你的 JWT 关键信息(payload)有一部分过期了,那么整个 JWT 将被视为无效,不能再使用了。