JWT 基础知识简介 简介 - 什么是 JWT?无状态?这意味着什么?进一步分解 - JWT 的组成部分 它如何保证安全?JWT 的缺点:注销用户/使令牌失效 关于复杂性、漏洞等的说明:其他资源:

2025-05-25

JWT 基础知识简介

简介——什么JWT?

无国籍?这是什么意思?

进一步细分——JWT 的组成部分

它如何保证安全?

JWT 的缺点:注销用户/使令牌无效

关于复杂性、漏洞等的注意事项:

其他资源:

如果你因为 JWT(JSON Web Tokens)听起来吓人或复杂而犹豫不决,那么我有个好消息要告诉你:它其实并不复杂。在这篇简短易懂的指南中,我将带你了解 JWT 的基础知识、它的独特之处,以及它与其他身份验证形式相比的优缺点。

简介——什么JWT?

JSON Web Tokens,或称 JWT,是一种“无状态”(稍后会详细介绍)的身份验证和会话管理方法,其中有关身份验证或会话的信息(用户是否是管理员?权限?等等)可以存储令牌本身内,而不是存储在服务器上。

本质上,您可以将 JWT 视为一个容器,其中装满了服务器发送的事实(在 JWT 术语中称为声明),以及有关如何存储这些“事实”的元数据(算法和类型)。

从最实际的角度来看,JWT 可以表示为单个字符串,您稍后会看到。

无国籍?这是什么意思?

JWT 并不总是无状态系统的一部分,但它们的组成方式使它们适合这种设置。

在典型的非 JWT 系统中,如果要处理身份验证并为不同的用户设置不同的权限,通常会结合使用会话和服务端存储——这就是状态。当用户登录时,您会session为其创建一个新的状态,并赋予其唯一的 ID。为了检查权限,您需要根据权限表和/或角色表查找他们的用户 ID。您加载的每个页面都可能需要多次数据库查找,以检查会话是否有效,然后获取角色,再获取权限等等。

相比之下,使用 JWT,所有关于用户有效性(已登录)及其角色和/或权限的信息都直接包含在 JWT 字符串本身中。服务器在接收到传入的 JWT 时,只需验证其有效性,而不必进行任何数据库查找。这可以使 JWT 成为无状态的。

然而这种方法也有缺点,稍后将会讨论。

进一步细分——JWT 的组成部分

JWT 看起来非常复杂,所以让我们逐个分解一下:

它们究竟长什么样?

首先,你可能想知道 JWT 到底是什么样子的。好吧,服务器最终发送到客户端的字符串,存储在localStoragesessionStorage或 cookie 中,可能如下所示(真实示例):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiQm9iYnkgVGFibGVzIiwiaWF0IjoxNTE2MjM5MDIyLCJpc0FkbWluIjp0cnVlLCJwZXJtaXNzaW9ucyI6eyJ1c2Vyc01ha2UiOnRydWUsInVzZXJzQmFuIjp0cnVlLCJ1c2Vyc0RlbGV0ZSI6ZmFsc2V9fQ.HFRcI4qU2lyvDnXhO-cSTkhvhrTCyXv6f6wXSJKGbFk

很有启发吧?好吧,一旦你明白了它的真正含义,就没那么可怕了:

base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(signature)

让我们进一步分解一下……

碎片

如上所示,JWT 由三个主要部分组成,每个部分在连接在一起之前都经过 base64 编码(使用网络安全变体,以避免出现可能破坏 URL 的字符)。除签名外,每个部分在编码前也是一个 JSON 对象,更多内容如下:

标头:

标头是包含有关 JWT 本身的元数据的部分。它至少包含用于加密签名的算法类型,但有时还会包含其他元数据,例如令牌本身的类型(JWT)。

例子:

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

在这个例子中,标题说“嘿,我是 JWT 类型的令牌,加密算法为 HS256!”

有效载荷:

就功能而言,而非安全性而言,有效载荷才是最重要的部分。有效载荷包含claims(或者我之前称之为“真相”),服务器表示该信息属于持有 JWT 的人,并且可以将其发送回服务器。

当用户通过标头、URL、cookie 等将 JWT 发送服务器时,服务器会解码有效负载并以数千种方式使用它;例如,允许用户执行“删除”操作,因为其中一项声明是他们是管理员。

有效载荷示例:

{
  "name": "Bobby Tables",
  "iat": 1516239022,
  "isAdmin": true,
  "permissions": {
     "usersMake": true,
     "usersBan": true,
     "usersDelete": false
  }
}

您的有效负载没有强制必须包含的密钥对,但有一些标准化且通用的密钥对。例如,iat是 的注册声明issued at,如果包含,则必须是一个代表 JWT 签发时间戳的数字。您应该避免与保留/注册的声明名称冲突。您可以在原始规范中阅读更多相关信息,点击此处

提醒:您应该尽量保持键和值简短,因为 JWT 的设计很小。

签名:

JWT 的签名是安全性方面最重要的部分。本质上,它是标头 + 有效负载的值,经过单向加密哈希函数处理,该函数使用只有服务器或其他受信任实体知道的密钥

如果使用 base64 解码该值,它看起来像乱码,因为它是一个安全的哈希:

.T\#..Ú\¯.uá;ç.NHo.´ÂÉ{ú.¬.H..lY

在服务器上生成此哈希的伪代码如下所示:

HMACSHA256(
  base64Url(header) + "." +
  base64Url(payload),
  SECRET_KEY
);

签名是服务器使用其密钥**验证用户发送的 JWT 是否有效以及是否由其自身或受信任的创建者创建的一种方式。

(PS:我在此处的示例中使用的密钥是krusty-krab-krabby-patty-secret-formula)。

** = 如果使用非对称加密,则可以使用公钥进行验证。请参阅“如何保证安全?”部分下的“签名算法”小节。

故障总结——重新组合

因此,为了总结并再次展示这些部分如何组合在一起,我们采取以下措施:

const header = {
  "alg": "HS256",
  "typ": "JWT"
};

const payload = {
  "name": "Bobby Tables",
  "iat": 1516239022,
  "isAdmin": true,
  "permissions": {
     "usersMake": true,
     "usersBan": true,
     "usersDelete": false
  }
};

...使用单向加密算法进行签名...

// Pseudo code
const signature = hashHS256(header, payload).withSecret(SECRET_KEY);

...最后,对每个部分进行 base64 转换后,将各部分连接成一个字符串:

// Pseudo code
const jwtString = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(signature);

它如何保证安全?

JWT 的安全机制在于其签名组件(如上所述)。进一步细分,当用户将 JWT 发送回服务器时,服务器会通过以下方式验证签名,以确保声明可信:

  1. 接收传入的标头和有效载荷
  2. 添加SECRET_KEY只有服务器和验证器知道**
  3. 将标头、有效载荷和秘密通过单向加密哈希
    • 这将创建一个服务器生成的签名
  4. 检查新创建的签名是否与用户在 JWT 中发送的签名匹配
  5. 如果它们匹配,则证明用户发送的 JWT 确实是使用服务器拥有的相同秘密创建的!

请注意,安全性的强度与您的密钥密切相关 - 使用适当长的密钥(以避免暴力破解尝试),并且永远不会共享。

附注:除了验证签名之外,服务器通常还会检查 JWT 是否符合预期的结构和标准。详情请参阅auth0 指南。

** = 或公钥,如果使用非对称加密,请参见下文。

签名算法

在这些示例中,我使用的是 HMAC,它是一种对称算法,这意味着只有一个私钥,没有公钥。为了让多方能够创建验证 JWT,他们必须拥有私钥。

使用非对称算法,虽然密钥仍然用于创建令牌,但公钥也可用于验证其有效性。这意味着其他服务器可以创建 JWT,而您的服务器仍然可以通过与公钥进行比对来验证它,而无需共享私钥/密钥。这种方法通常是首选,因为它允许多方进行验证,而只有拥有私钥的人才可以真正创建令牌。

我不会在这里进一步讨论细节,因为这应该是一个介绍,但你可以在这里阅读更多内容。

JWT 的缺点:注销用户/使令牌无效

到目前为止,JWT 似乎比有状态身份验证方法具有许多优势,但我们尚未讨论的是注销用户、删除用户或以其他方式使令牌无效或撤销。

事实上,这正是 JTW 的不足之处。为了使 JWT 失效,你需要某种数据库/状态系统,因为你最终要做的就是维护黑名单或白名单。

有了黑名单,每当您想要使令牌无效时,您都会将其添加到黑名单表中,并且每当用户尝试使用 JWT 时,您总是需要检查它不在黑名单上。

使用白名单,基本原理相同,但每个 token 在创建时都会添加到白名单中,并在失效时从白名单中移除。并且,每个传入的 JWT 只有在白名单中才会被接受

如果您必须使用其中任何一种方法,在我看来,这表明您的需求与 JWT 所能提供的不一致,并且可能是时候重新考虑您的网站架构,看看是否有更好的选择。

避免状态:自动过期会话

对于避免必须实施用于注销用户的有状态 JWT 系统的网站来说,一个相当常见的解决方法是快速自动使令牌过期。

例如,您可以将 JWT 的创建时间或计划的过期时间放入其自身的有效负载中。然后,在验证传入的 JWT 时,只需检查过期时间是否早于或等于当前时间 - 如果是,则拒绝该 JWT。

问题在于,它仍然只是权宜之计,最终可能比一开始就使用会话复杂。为了避免用户在使用网站时不断被注销(真烦人!),你需要使用自动刷新的 JWT,可能通过Auth0 的 Refresh Tokens之类的服务来实现。这仍然不支持手动注销流程,除非你同时引入撤销功能作为刷新模式的一部分。

避免状态:惰性失效

想象一下,你需要大幅升级你的用户管理系统,并强制所有人切换。不幸的是,网上很多开发者都建议一种不太理想的解决方法,那就是……更改你的私钥。如果你这样做,所有流通的 JWT 都会立即失效,并强制所有人重新登录,因为每个人的 JWT 的签名部分将不再匹配。

关于复杂性、漏洞等的注意事项:

关于 JWT,我还有很多内容没有在这篇文章中完整讲解(我找个借口,这篇文章应该只是个介绍)。事实上,许多开发者使用第三方服务(又称联合身份管理服务),例如Auth0,来管理 JWT 的大部分功能。这并不能神奇地解决我之前提到的许多 JWT 问题,但它可以让使用起来更容易,并抽象出一些复杂性和安全隐患。


其他资源:

什么 类型 关联
维基百科页面 - 令人惊讶的是专注于开发 维基百科 维基百科
IETF 的 RFC 规范 规格 RFC #7519
JWT 调试器、库等 游乐场和快速参考 jwt.io

附言:如果您喜欢本指南的写作风格,您可能会喜欢我的其他一些速查表和快速参考指南。我一直在收集它们并在这里展示。

文章来源:https://dev.to/joshuatz/simple-intro-to-jwt-basics-2kd8
PREV
为网站开发人员提供免费托管服务
NEXT
使用 Python、Django 和 Django Rest Framework 开发 Restful API 简介 使用 Django Rest Framework 开发 REST API 源代码 奖励