JWT一文全解,禁止这样使用,被发现立即领盒饭!

哈根达斯
2024-12-24 / 0 评论 / 13 阅读 / 正在检测是否收录...

在当今的网络开发领域,JSON Web Tokens(JWT)被广泛提及和应用,但对于其在存储 Session 方面的适用性,却存在诸多误解。本文旨在深入探讨为何 JWT 并不适合担当存储 Session 的重任,并揭示其可能引发的安全问题,以期帮助开发者们更全面、准确地理解和运用 JWT。

一、术语明确

在展开讨论之前,先对一些关键术语进行清晰的界定,这有助于避免后续的混淆和歧义:

  • 无状态 JWT(Stateless JWT):这类 JWT Token 直接将 Session 数据编码于其中,所有与 Session 相关的信息都被封装在 Token 内部。
  • 有状态 JWT(Stateful JWT):此类型的 JWT Token 仅包含对 Session 的引用或者 Session ID,实际的 Session 数据则存放在服务端。
  • Session token(亦称 Session cookie):这是一种标准的、可被签名的 Session ID 形式,许多 Web 框架(如 Laravel)长期以来都采用这种成熟的 Session 机制,其 Session 数据同样是存储在服务端。

需要着重强调的是,本文的目的并非倡导“绝对不使用 JWT”,而是要明确指出 JWT 在作为 Session 机制时存在的弊端和风险。实际上,JWT 在其他特定场景下仍具有其独特的价值和用途,在本文结尾部分,将会简要介绍一些 JWT 的合理应用场景。

二、对比对象的澄清

常常有人错误地将 Cookies 和 JWT 进行不恰当的比较,这就如同将内存和硬盘简单对比一样,缺乏实际意义。Cookies 本质上是一种存储机制,而 JWT Tokens 则是经过加密和签名处理的令牌,二者并非相互对立的关系,相反,它们既可以独立使用,也能够结合运用。正确的比较方式应该是:Session 与 JWT 相对比,以及 Cookies 与 Local Storage 相对比。在后续的讨论中,本文将主要围绕 JWT Tokens 和 Session 的对比展开,并在必要时涉及 Cookie 和 Local Storage 的比较,这样的对比方式更能准确地反映实际情况和问题本质。

三、JWT 被广泛宣扬的优势剖析

(一)易于水平扩展?

在众多关于 JWT 的宣传中,易于水平扩展被列为其优势之一。从技术层面来看,这一说法在使用无状态 JWT Tokens 时确实存在一定的合理性,但前提是要有相应的应用场景需求。然而,在实际情况中,绝大多数项目几乎并不需要这种高度的横向扩展能力。在常见的系统扩展场景中,存在许多更为简便易行的解决方案。例如:

  • 当需要在单台服务器上运行多个后端进程时,只需在该服务器上部署 Redis 服务来存储 Session 即可,这种方式简单高效,能够满足大部分常规业务的需求。
  • 对于运行多台服务器的情况,配备一台专门的 Redis 服务器用于集中存储 Session 数据,就能轻松实现 Session 的共享和管理,确保系统的稳定运行。
  • 在更为复杂的多集群环境中,通过会话保持(即粘滞会话)技术,也能够很好地应对 Session 的管理问题,而且这些技术在现有的软件系统架构中都得到了良好的支持,通常不需要应用层进行特殊的复杂处理。

或许有人会考虑为未来的业务发展预留更多的调整空间,担心以后可能会需要无状态的会话机制。但从实际经验来看,后期对 Session 机制进行替换并非难事,唯一可能产生的影响是在迁移过程中所有用户需要重新登录一次。因此,综合考虑 JWT 可能带来的负面影响,在项目前期并没有必要为了一个不太确定的未来需求而强行采用 JWT 来实现 Session 管理。

另外,对于近期有面试跳槽计划的开发者,建议在[Java面试库]()小程序上进行在线刷题,该小程序涵盖了 3000 余道 Java 面试题,几乎囊括了所有主流技术面试题,能够帮助开发者更好地准备面试,提升自身竞争力。

(二)易于使用?

实际上,JWT 在使用便利性方面并没有优势。使用 JWT 时,无论是在客户端还是服务端,都需要开发者自行处理 Session 的管理逻辑。相比之下,标准的 Session cookies 则具有开箱即用的特点,许多 Web 框架都对其进行了良好的封装和支持,开发者无需过多关注底层的实现细节,即可轻松使用 Session 功能。而 JWT 目前尚未被广泛集成到各种开箱即用的框架中,这就意味着研发人员需要投入更多的精力来处理与 JWT 相关的复杂逻辑,从这个角度来看,JWT 并不比传统的 Session 机制更易于使用。

(三)更加灵活?

关于 JWT 更加灵活这一说法,目前并没有确凿的证据和清晰的阐述来支撑。在实际应用中,几乎每个主流的 Session 实现都允许开发者直接将数据存储到 Session 中,这种数据存储和管理方式与 JWT 的机制并没有本质上的区别。因此,所谓的“JWT 更加灵活”更多的可能只是一种流行但缺乏实际依据的说法,在实际的开发过程中,开发者很难真切地感受到这种灵活性带来的实际价值和优势。

(四)更加安全?

有不少人认为 JWT Tokens 更加安全,其主要理由是 JWT 使用了加密技术。然而,这种观点是不准确的。实际上,签名后的 Cookies 相较于未签名的 Cookies 同样具有更高的安全性,而且这并不是 JWT 所独有的特性,许多优秀的 Session 实现都采用了签名后的 Cookies 来保障数据的安全传输和完整性(例如 Laravel 框架)。

需要明确的是,单纯的“使用加密技术”并不能自动使某个事物变得更加安全,加密技术必须服务于特定的安全目标,并且要确保其应用方式是针对该目标的有效解决方案。如果加密技术使用不当,反而可能会引入新的安全风险,导致安全性降低。

此外,还有一种关于“JWT 更加安全”的常见论述,即“JWT 不使用 Cookies 传输 Tokens”,这种说法是荒谬的。Cookie 仅仅是一条 HTTP 头信息,使用 Cookies 本身并不会导致不安全的情况发生。事实上,Cookies 在设计和实现上受到了特别良好的保护,能够有效地防止恶意的客户端代码对其进行篡改和窃取。

如果担心 Session cookies 被他人拦截,正确的做法是采用 TLS(Transport Layer Security)加密协议来保障数据传输的安全。如果不使用 TLS,那么无论是哪种类型的 Session 机制,包括 JWT,都存在被拦截和窃取的风险,因为在未加密的网络环境中,数据的安全性无法得到有效的保障。

(五)内置过期时间功能?

JWT 的内置过期时间功能实际上并没有太大的实际价值和优势。虽然 JWT 可以在 Token 中设置过期时间,但在服务端同样能够轻松实现对 Session 过期时间的控制,而且许多现有的 Session 实现正是采用这种方式。相比之下,服务端的过期控制更加合理和灵活,因为这样可以让应用程序及时清除那些不再需要的 Session 数据,从而优化系统资源的利用和管理。而如果依赖无状态 JWT Tokens 的过期机制,由于其无法在服务端直接对过期的 Token 进行有效的管理和清理,可能会导致一些潜在的问题,例如 Session 数据的残留和浪费,以及在某些情况下可能会增加系统的复杂度和管理难度。

(六)无需询问用户“本网站使用 Cookies”?

这种说法是完全错误的。并不存在所谓的“Cookies 法律”专门针对 Cookies 的使用进行限制,实际上,有关 Cookies 的各种法律法规涵盖的范围更广,包括任何类型的“对某项服务的正常运行非严格必须的持久性 ID”,这意味着几乎所有可以想到的 Session 机制都在其监管范围内。

具体来说:

  • 如果 Session 或 Token 是出于系统功能目的(例如保持用户的登录状态)而使用的,那么无论采用何种方式存储 Session,都无需征得用户的同意,因为这是保障系统正常运行所必需的功能。
  • 然而,如果 Session 或 Token 是用于其他目的(例如数据分析、用户追踪等),那么无论选择怎样的 Session 存储机制,都需要明确询问用户是否允许使用,以确保符合相关法律法规的要求和保护用户的隐私权益。

(七)防止 CSRF 攻击?

JWT 并不能有效地防止 CSRF(Cross-Site Request Forgery)攻击。目前,存储 JWT Tokens 主要有两种常见方式:

  • 一种是将其存入 Cookie 中,但这样做仍然容易受到 CSRF 攻击,与传统的 Session 机制类似,仍然需要采取特殊的防护措施来保护 Token 不被恶意利用,例如使用 CSRF Tokens 等技术来增强安全性。
  • 另一种方式是将 JWT 存储在其他地方,如 Local Storage 中。虽然这种方式在一定程度上可以避免 CSRF 攻击,但却带来了新的问题。因为使用 Local Storage 需要依赖 JavaScript 来获取和使用 Token,这就要求网站必须支持 JavaScript 才能正常访问,而且这还可能引发另一个完全不同且可能更加严重的漏洞,后续将会对此进行详细说明。

需要明确的是,预防 CSRF 攻击的唯一正确方法是使用 CSRF Tokens,而 Session 机制本身与防止 CSRF 攻击并没有直接的关联,不能将防止 CSRF 攻击的期望寄托于 JWT 或其他 Session 机制上,而应该采用专门的 CSRF 防护技术来保障系统的安全。

(八)更适用于移动端?

这种说法是毫无根据的。目前市面上几乎所有可用的浏览器都对 Cookies 提供了良好的支持,因此也能够很好地支持基于 Cookies 的 Session 机制。同样,主流的移动端开发框架以及严谨的 HTTP 客户端库也都对 Session 机制有着完善的支持,无论是在移动端还是桌面端,Session 机制都能够稳定、高效地运行,不存在 JWT 在移动端具有独特优势的情况,所以这并不是一个选择 JWT 作为 Session 机制的合理理由。

(九)适用于阻止 Cookies 的用户?

这种情况发生的可能性非常低。通常情况下,用户如果选择阻止 Cookies,往往也会阻止其他任何形式的持久化数据存储,而不仅仅是针对 Cookies 本身。例如,Local Storage 以及其他能够实现 Session 持久化存储的机制(无论是否使用 JWT)都会受到影响。因此,即使采用 JWT 来试图解决用户阻止 Cookies 的问题,也很难达到预期的效果,因为这是一个更为复杂和独立的用户行为和隐私设置问题,与具体的 Session 存储技术关系不大。

而且,对于大多数禁用 Cookies 的用户来说,他们往往清楚这会导致网站的身份认证功能无法正常使用,因此在实际使用中,他们通常会针对自己关心的特定站点单独解锁 Cookies 功能,以确保能够正常访问和使用这些网站的服务。所以,从开发者的角度来看,这并不是一个需要通过采用特殊技术(如 JWT)来解决的关键问题,更好的做法是向用户清晰、详细地解释为什么网站需要使用 Cookies 才能正常运行,以提高用户对 Cookies 使用的理解和接受程度。

四、JWT 的劣势揭示

通过对上述常见误解的分析和澄清,我们已经了解到 JWT 在作为 Session 机制时,所谓的优势大多并不成立。然而,问题远不止于此,使用 JWT 作为 Session 机制还存在许多严重的缺点,其中一些甚至会引发重大的安全问题,以下将对这些劣势进行详细阐述:

(一)更费空间

JWT Tokens 的实际数据量并不小,尤其是在使用无状态 JWT 时,由于所有的 Session 数据都需要直接编码到 Tokens 内部,这很容易导致 Token 的长度迅速增加,从而可能超过 Cookies 或 URL 的长度限制。在这种情况下,可能会有人考虑将 JWT Tokens 存储到 Local Storage 中,但这又会引发其他一系列问题,后续将会详细说明。

(二)更不安全

当将 JWT Tokens 存储到 Cookies 中时,其安全性与其他传统的 Session 机制相比并没有本质上的提升或差异。然而,如果将 JWT 存储在其他位置,如 Local Storage,就会引入一个新的安全漏洞。具体来说,Local Storage 是 HTML5 提供的一项功能,它允许浏览器进行 Key/Value 形式的存储操作,对于可能较大的 JWT Tokens 来说,由于 Cookies 的存储容量通常在 4k 左右,对于较大的 Tokens 可能无法满足存储需求,而 Local Storage 似乎成为了一个可行的解决方案。

但需要注意的是,Local Storage 并没有像 Cookies 那样提供完善的安全保护措施。与 Cookies 不同,Local Storage 中的数据不会在每次 HTTP 请求时自动发送到服务器端,获取这些数据的唯一途径是通过 JavaScript 代码。这就意味着,如果攻击者能够成功注入恶意的 JavaScript 脚本,并且该脚本能够通过网站的内容安全策略检查,那么攻击者就可以轻易地访问和泄露 Local Storage 中存储的 JWT Tokens,从而导致严重的安全事故。

此外,JavaScript 在处理数据时并不关心或追踪数据是否通过安全的 HTTPS 连接进行传输,对于 JavaScript 而言,这些数据仅仅是普通的数据,浏览器会按照常规的数据处理方式来对待它们。这与我们长期以来通过各种技术手段确保 Cookies 安全的努力背道而驰,因为我们花费了大量的精力来防止 Cookies 被恶意接触和窃取,而现在使用 Local Storage 存储 JWT Tokens 却忽视了这些宝贵的经验教训,这无疑是一种安全意识上的倒退。

综上所述,无论是否采用 JWT,“使用 Cookies 仍然是保障数据安全传输和存储的重要手段”,不应该轻易地放弃 Cookies 而选择其他可能存在安全隐患的存储方式。

(三)无法单独销毁

使用 JWT 作为 Session 机制还存在一个严重的安全问题,那就是无法单独销毁 Session。与传统的 Sessions 不同,传统 Sessions 可以在服务端随时根据需要进行单独的销毁操作,例如当检测到用户的异常行为或安全威胁时,能够及时销毁相应的 Session,以防止潜在的风险。然而,无状态 JWT Tokens 由于其设计特点,在 Token 过期之前无法被单独地销毁。

例如,在检测到攻击行为时,由于无法立即销毁攻击者的 Session,这就使得攻击者仍然可以利用有效的 Token 继续访问系统,从而给系统带来持续的安全威胁。同样,在用户修改密码等关键操作后,也无法及时销毁旧的 Sessions,这可能会导致旧的 Token 仍然被泄露或滥用,进一步增加了系统的安全风险。

针对这一问题,目前几乎没有有效的解决方案,除非重新构建一套复杂的、有状态(Stateful)的基础设施,专门用于明确地检测和拒绝特定的 Session,但这样做不仅成本高昂,而且完全违背了使用无状态 JWT Tokens 的初衷和优势,使得系统的架构变得更加复杂和难以维护。

(四)数据延迟

与上述安全问题相关联的是,使用无状态 JWT Tokens 还可能存在数据延迟的问题,这也构成了一个潜在的安全隐患。由于无状态 Tokens 中存储的数据是在某个特定时间点生成的,随着时间的推移,这些数据可能会逐渐“过时”,无法反映数据库中最新的信息。

例如,用户在个人信息页面修改了某些数据,如个人资料图片的 URL,但由于 JWT Token 中仍然保留着旧的 URL,这就可能导致在某些情况下,系统仍然使用旧的、不准确的信息进行操作,从而出现数据不一致的情况。更为严重的是,如果涉及到权限相关的信息,例如用户原本具有管理员权限,但后来该权限被撤销,然而由于无法销毁之前颁发的具有管理员权限的 Token,系统可能仍然会认为该用户具有管理员权限,这就可能导致严重的安全漏洞,除非关闭整个系统,否则很难完全避免这种情况的发生。

(五)实现库缺乏生产环境验证或压根不存在

在考虑使用 JWT 作为 Session 机制时,还需要关注其实现库的成熟度和稳定性问题。许多人可能认为上述提到的问题主要是针对无状态 JWT,但实际上,即使使用有状态 Tokens,虽然在功能上与传统的 Session cookies 大致相当,但目前市场上缺乏经过大量生产环境验证的成熟实现库。

相比之下,现有的许多传统 Session 实现(例如适用于 Express 的 express-sessionhttps://github.com/expressjs/sessio)已经在生产环境中使用了多年,其安全性和稳定性经过了大量实际项目的检验和改良,开发者可以放心地使用这些成熟的实现,并且能够受益于社区的持续支持和更新。而如果选择使用 JWT 作为 Session cookies 的临时替代品,不仅无法享受到这些成熟实现带来的好处,而且还需要自行不断改进和完善其实现细节,这在过程中很容易引入新的漏洞和问题。即使选择使用第三方的 JWT 实现库,由于这些库尚未在真实世界中得到广泛的应用和验证,仍然存在一定的风险和不确定性。

例如,Laravel Passport 虽然采用了类似“有状态 JWT”的方式来存储 OAuth Access Token,但幸运的是,Passport 已经在一些实际应用中得到了验证,并且不完全依赖于 JWT 的原生特性,而是结合了其他成熟的技术和机制来保障安全性和稳定性。

五、结论

综上所述,无状态 JWT Tokens 在作为 Session 机制时存在诸多无法忽视的问题,如无法单独销毁或更新、可能导致长度限制问题以及潜在的安全隐患等。而有状态 JWT Tokens 虽然在功能上与传统的 Session cookies 相似,但由于缺乏广泛的生产环境验证、经过大量 Review 的成熟实现以及良好的客户端支持,其在实际应用中的可靠性和稳定性仍然存在一定的疑虑。

因此,除非是在像 BAT 这样规模庞大、具有特殊技术需求和挑战的公司,对于大多数普通的项目和开发者来说,并没有充分的理由选择使用 JWT 作为 Session 机制。在实际开发中,建议仍然优先选择传统的、经过长期实践验证的 Session 机制,这样能够更好地保障系统的安全性、稳定性和可维护

另外,近期才哥整理出了一个可用于快速刷面试题的小程序,其中收录了常见面试题及其答案,涵盖了基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型,感兴趣的可以点击下方试试

才哥IT刷题小程序

0

评论 (0)

取消