英文原文地址: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编码的。
抽象级别表示:
header.payload.signature
令牌示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOixMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMYJYHW-NW
让我们描述定义JWT的三个部分。
第一部分-标头
第一部分称为JOSE(Javascript对象签名和加密)标头,其中包含有关其自身的声明。这些声明建立了所使用的算法(无论JWT是签名的还是加密的),以及通常如何解析其余JWT的算法。
在我们的示例中,标头是:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
这是以下JSON的Base64Url编码形式:
{
"alg": "HS256",
"typ": "JWT"
}
alg声明标识用于对JWT进行签名的加密算法,而typ声明用于声明JWT的媒体类型(可选参数,仅用于在JWT可能与其他带有其他对象的对象混合使用时提供帮助JOSE标头,但是建议将其JWT
默认设置为)。
根据所讨论的JWT的类型,标头中可能有更多要求是强制性的。身份验证标头中可以使用以下字段:
- typ —令牌类型(RFC 7515的4.1.9节)
- cty —内容类型(RFC 7515的4.1.10节)
- alg —算法(RFC 7515的4.1.1节)
- jku — JWK设置URL(RFC 7515的4.1.2节)
- jwk — JSON Web密钥(RFC 7515的4.1.3节)
- kid —密钥ID(RFC 7515第4.1.4节)
- x5u — X.509 URL(RFC 7515第4.1.5节)
- x5c — X.509证书链(RFC 7515的4.1.6节)
- x5t — X.509证书SHA-1指纹(RFC 7515第4.1.7节)
- x5t#S256 — X.509证书SHA-256指纹(RFC 7515第4.1.8节)
- 暴击—严重(RFC 7515的4.1.11节)
尽管通常使用范围有限,但可以向标头添加其他用户定义的声明。
第二部分-有效负载
第二部分是实际数据,通常会在其中添加所有用户信息。规范中定义的某些权利要求也可能存在,但都不是强制性的。索赔有以下三种类型:注册的,公共的和私人索赔。
- 注册的索赔名称-一组非强制性但建议使用的预定义索赔,以提供一组有用的,可互操作的索赔。
- 公共声明名称-可以由使用JWT的人员随意定义,但是为了避免冲突,任何声明名称都应在IANA“ JSON Web令牌声明”注册表中注册,或者应该是抗冲突的名称(这种名称不太可能与其他名称冲突)
- 私人声明名称-由消费者和生产者(您的应用程序)定义,可以是任何名称
这些是JWT注册索赔规范中定义的7个属性,这意味着您无法以其他方式解释这些属性的值
- iss —颁发者(RFC 7519的4.1.1节)
- sub —主题(RFC 7519的4.1.2节)
- aud —受众群体(RFC 7519的4.1.3节)
- exp —到期时间(RFC 7519的4.1.4节)
- nbf-不早于此(RFC 7519第4.1.5节)
- iat —发行于(RFC 7519的4.1.6节)
- jti — JWT ID(RFC 7519的4.1.7节)
除了这7个属性外,您还可以根据需要定义任何属性(也称为私有或公共声明)。在我们的示例中,有效负载是eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9
以下JSON的Base64Url编码形式:
{
"sub": "1234567890",
"name": "John",
"iat": 1516239022
}
该有效负载具有两个已注册的声明(sub
和iat
)和一个公共声明(name
)。不要将诸如密码之类的敏感数据放入有效负载中,因为它仅是base64编码的,并且未加密。这意味着检索令牌的任何人都可以通过简单的base64对其进行解码来查看有效内容。
您可能已经注意到的另一件事是,所有声明名称都只有三个字符,只要JWT紧凑即可。
第三部分-签名
JWT的第三部分是签名,它也是base64url编码的。它是已编码的标头,已编码的有效负载,秘密以及最后在标头中指定的算法的总和。签名的目的是检查报头或有效载荷是否未更改,并检查发送方是否是需要的人。
签名的计算方法如下:
header = {...}
payload = {...}
encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
signature = base64UrlEncode(HMACSHA256(encodedString, 'secret'));
HMAC(密钥哈希消息认证代码)是一种接收消息,密钥和哈希函数,并将密钥与消息数据混合,并使用哈希函数对结果进行哈希处理的函数。你能想到的HMACSHA256(message, secret)
作为SHA256(message + secret)
,虽然这不是100%正确,HMAC算法是多一点比复杂。您可以在这里了解更多信息
在我们的特定示例中,我们为标头eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
和有效负载提供了base64url编码版本eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9
。为了生成签名,我们现在将HMACSHA256应用于这些字符串的串联版本,然后base64url对结果进行编码。
hash = HMACSHA256('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9', 'secret')
signature = base64UrlEncode(hash)
// 4hrUcwXkLeE3O-pwPHgS860Nt-Lq_3PNoyHXnBTyjIY
放在一起
我们有标题
header = {
"alg": "HS256",
"typ": "JWT"
}
有效载荷
payload = {
"sub": "1234567890",
"name": "John",
"iat": 1516239022
}
和签名
encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
signature = HMACSHA256(encodedString, 'secret');
最终令牌的计算方式如下:
jwtToken = base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + base64UrlEncode(signature)
认证流程
- 用户向认证服务提交用户名和密码
- 身份验证服务将验证凭据,并生成一个用密钥字符串和包含用户标识符以及到期/持续时间时间戳记的有效负载签名的JWT令牌
- 客户端(浏览器)将令牌存储在本地存储或cookie中,或以后可以检索到的任何位置
- 当用户想要检索受保护的资源时(例如:导航到受保护的页面),浏览器将需要在对我们服务器的每个HTTP请求中包含令牌(通常通过Authentication标头)
- 服务器检查签名是否有效-它从提供的令牌的标头和有效载荷中生成新的签名,然后检查其是否与提供的令牌中的签名匹配
- 服务器从JWT令牌获取用户标识符,并相应地处理HTTP请求
无担保的JWT
不安全的JWT是没有指定算法且其JWT签名值带有空字符串的令牌
标头很简单:
{
"alg": "none"
}
整个令牌可能看起来像:
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上找到我,在那里我可以构建很棒的东西。
谢谢阅读!
附注:如果您喜欢这篇文章,那么如果您按“推荐”按钮或与朋友分享,那将有很多意义。