image.png

英文原文地址:https://medium.com/swlh/how-jwt-works-in-depth-604c93ec20a4 作者:亚历克斯·坎伯斯 翻译:高行行 公众号:《骇客与画家》

为什么以及如何运作?从头开始理解并构建一个简单的JWT库。(第1部分)

这是教程的第2部分系列的第1部分。您可以在此处访问第2部分

我们将讨论的主题

  • 什么是JWT
  • 用在哪里
  • JWT结构
  • 第一部分-标头
  • 第二部分-有效负载
  • 第三部分-签名
  • 无担保的JWT
  • JWT与会议
  • 结论

什么是JWT

JSON Web令牌是一种紧凑且独立的方法,用于表示要在两方之间转移的声明。声明被编码为用于传输数据的JSON对象。传输的信息是经过数字签名的,因此可以验证和信任。

JWT有两种类型:

JWS(JSON Web签名):用于签名数据,使其受到完整性保护,这意味着:

  • 中间人攻击可以查看数据的含义
  • 中间人攻击无法修改它,因为签名验证将失败

JWE(JSON Web加密):用于加密数据并使其具有完整性保护

  • 中间人攻击无法查看数据到底是什么
  • 中间人攻击无法修改它,因为验证将失败

您可以将JWT视为抽象类,而JWS和JWE是具体的实现。

在本文中,我们将介绍JSON Web签名(JWS)规范的实现,因为它是最流行的。

使用地点

JWT有多种应用。其中一些是:

  • 客户端/无状态会话-我们可以创建一个签名的令牌,其中包含识别用户所需的数据,而不是生成唯一的会话ID,而是将其存储在服务器端并将其交还给用户。
  • 联合身份-SAML和OpenID Connect-可能无关的各方可以与其他方共享身份验证和授权服务。

JWT结构

每个部分都用句点分隔。第一部分和第二部分分别是base64url编码的,最后一部分是签名,也是base64url编码的。

抽象级别表示:

  1. header.payload.signature

令牌示例:

  1. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOixMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMYJYHW-NW

让我们描述定义JWT的三个部分。

第一部分-标头

第一部分称为JOSE(Javascript对象签名和加密)标头,其中包含有关其自身的声明。这些声明建立了所使用的算法(无论JWT是签名的还是加密的),以及通常如何解析其余JWT的算法。

在我们的示例中,标头是:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9这是以下JSON的Base64Url编码形式:

  1. {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }

alg声明标识用于对JWT进行签名的加密算法,而typ声明用于声明JWT的媒体类型(可选参数,仅用于在JWT可能与其他带有其他对象的对象混合使用时提供帮助JOSE标头,但是建议将其JWT默认设置为)。

根据所讨论的JWT的类型,标头中可能有更多要求是强制性的。身份验证标头中可以使用以下字段:

尽管通常使用范围有限,但可以向标头添加其他用户定义的声明。

第二部分-有效负载

第二部分是实际数据,通常会在其中添加所有用户信息。规范中定义的某些权利要求也可能存在,但都不是强制性的。索赔有以下三种类型:注册的公共的私人索赔。

  • 注册的索赔名称-一组非强制性但建议使用的预定义索赔,以提供一组有用的,可互操作的索赔。
  • 公共声明名称-可以由使用JWT的人员随意定义,但是为了避免冲突,任何声明名称都应在IANA“ JSON Web令牌声明”注册表中注册,或者应该是抗冲突的名称(这种名称不太可能与其他名称冲突)
  • 私人声明名称-由消费者和生产者(您的应用程序)定义,可以是任何名称

这些是JWT注册索赔规范中定义的7个属性,这意味着您无法以其他方式解释这些属性的值

除了这7个属性外,您还可以根据需要定义任何属性(也称为私有或公共声明)。在我们的示例中,有效负载是eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9以下JSON的Base64Url编码形式:

  1. {
  2. "sub": "1234567890",
  3. "name": "John",
  4. "iat": 1516239022
  5. }

该有效负载具有两个已注册的声明(subiat)和一个公共声明(name)。不要将诸如密码之类的敏感数据放入有效负载中,因为它仅是base64编码的,并且未加密。这意味着检索令牌的任何人都可以通过简单的base64对其进行解码来查看有效内容。

您可能已经注意到的另一件事是,所有声明名称都只有三个字符,只要JWT紧凑即可。

第三部分-签名

JWT的第三部分是签名,它也是base64url编码的。它是已编码的标头,已编码的有效负载,秘密以及最后在标头中指定的算法的总和。签名的目的是检查报头或有效载荷是否未更改,并检查发送方是否是需要的人。

签名的计算方法如下:

  1. header = {...}
  2. payload = {...}
  3. encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
  4. signature = base64UrlEncode(HMACSHA256(encodedString, 'secret'));

HMAC(密钥哈希消息认证代码)是一种接收消息,密钥和哈希函数,并将密钥与消息数据混合,并使用哈希函数对结果进行哈希处理的函数。你能想到的HMACSHA256(message, secret)作为SHA256(message + secret),虽然这不是100%正确,HMAC算法是多一点比复杂。您可以在这里了解更多信息

在我们的特定示例中,我们为标头eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9和有效负载提供了base64url编码版本eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9。为了生成签名,我们现在将HMACSHA256应用于这些字符串的串联版本,然后base64url对结果进行编码。

  1. hash = HMACSHA256('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9', 'secret')
  2. signature = base64UrlEncode(hash)
  3. // 4hrUcwXkLeE3O-pwPHgS860Nt-Lq_3PNoyHXnBTyjIY

放在一起

我们有标题

  1. header = {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }

有效载荷

  1. payload = {
  2. "sub": "1234567890",
  3. "name": "John",
  4. "iat": 1516239022
  5. }

和签名

  1. encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
  2. signature = HMACSHA256(encodedString, 'secret');

最终令牌的计算方式如下:

  1. jwtToken = base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + base64UrlEncode(signature)

认证流程

  1. 用户向认证服务提交用户名和密码
  2. 身份验证服务将验证凭据,并生成一个用密钥字符串和包含用户标识符以及到期/持续时间时间戳记的有效负载签名的JWT令牌
  3. 客户端(浏览器)将令牌存储在本地存储或cookie中,或以后可以检索到的任何位置
  4. 当用户想要检索受保护的资源时(例如:导航到受保护的页面),浏览器将需要在对我们服务器的每个HTTP请求中包含令牌(通常通过Authentication标头)
  5. 服务器检查签名是否有效-它从提供的令牌的标头和有效载荷中生成新的签名,然后检查其是否与提供的令牌中的签名匹配
  6. 服务器从JWT令牌获取用户标识符,并相应地处理HTTP请求

无担保的JWT

不安全的JWT是没有指定算法且其JWT签名值带有空字符串的令牌

标头很简单:

  1. {
  2. "alg": "none"
  3. }

整个令牌可能看起来像:

  1. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9.

请注意,令牌字符串上没有签名(格式为header.payload.)。对JWT的常见攻击是将标头alg更改为none,然后剥离签名,从而使其成为已使用的JWT。应用程序负责确保对JWT进行相应的验证。

JWT与会议

JWT不能说每个会话都使用“会话”。JWT提供了在客户端而不是在服务器上维护会话状态的方法。在这里,我将重点介绍每种方法的优缺点,但这实际上取决于您的应用程序。

智威汤逊

优点:

  • 经济利益,您无需拥有带有会话的数据库
  • 因为JWT可以携带有效载荷,所以可以完成一些聪明的事情,例如提醒用户其会话将在注销前几天过期
  • 真正的无状态RESTful API

缺点:

  • 他们占用更多空间
  • 数据过时了(admin即使您刚刚撤销了某人的角色,该人仍可能拥有一个令牌,其admin角色为)
  • 您不能使单个JWT令牌无效

会议:

优点:

  • 不透明:第三方无法从中提取任何数据
  • 以编程方式注销用户的能力

缺点:

  • 对于复杂的体系结构,将数据从一个中央存储传递到其他所有服务可能既麻烦又昂贵
  • 使用内存解决方案,您可以限制水平缩放

结论

JSON Web令牌是一种提供无状态授权和信息交换方式的工具。它利用加密技术来确保各方之间的完整性和安全性。

因为是对事物在较低级别如何工作有一个总体了解的一种好习惯,所以本文的第二部分致力于实现一个实验性NodeJS库,该库实现了JWT标准的基本且最基本的部分。

仅此部分而已。在下一部分中,我们将看到如何通过在NodeJS中构建一个简单的库来创建,签名,验证和解码JWT。

本文的第2部分:

JWT的工作原理-深入为什么以及如何运作?从头开始理解并构建一个简单的JWT库。(第2部分)medium.com

您可以在GitHub上找到我,在那里我可以构建很棒的东西。

谢谢阅读!

附注:如果您喜欢这篇文章,那么如果您按“推荐”按钮或与朋友分享,那将有很多意义。