深入理解JWT认证:从原理到实践(上)

摘要

在现代Web应用中,用户认证和授权是不可或缺的核心功能。随着前后端分离和分布式系统的普及,传统的Session-Cookie认证机制面临着跨域、扩展性等挑战。JSON Web Token(JWT)作为一种轻量级、自包含的开放标准,正逐渐成为主流的认证解决方案。本文将以掘金博主的视角,深入剖析JWT的底层原理、核心结构、工作流程以及安全性考量,旨在帮助开发者全面理解JWT的精髓,为构建安全、高效的Web应用奠定基础。

1. 引言:无状态HTTP与认证的挑战

HTTP协议是无状态的,这意味着服务器在处理两次独立的请求时,无法直接识别它们是否来自同一个用户。在用户认证场景中,这带来了巨大的挑战:用户登录后,如何在后续的每次请求中都证明其身份,而无需重复提交用户名和密码?

传统的解决方案是基于Session-Cookie机制:

  1. 登录:用户提交用户名和密码到服务器。
  2. 创建Session:服务器验证凭据后,创建一个Session(会话),并将Session ID存储在服务器内存或数据库中。
  3. 设置Cookie:服务器将Session ID通过Cookie发送给客户端。
  4. 后续请求:客户端在后续的每次请求中,都会自动携带这个Cookie(包含Session ID)。
  5. 验证Session:服务器接收到请求后,从Cookie中读取Session ID,并在服务器端查找对应的Session,从而识别用户身份。

Session-Cookie机制简单易用,但在以下场景中暴露出局限性:

  • 跨域问题:Cookie默认受同源策略限制,跨域请求需要额外配置CORS,增加了复杂性。
  • 分布式部署:在多服务器、负载均衡的环境下,Session共享成为难题,需要引入额外的Session存储方案(如Redis),增加了系统复杂度。
  • 移动应用/API认证:移动应用通常不依赖Cookie,需要更灵活的认证方式。
  • CSRF攻击:Cookie容易受到跨站请求伪造(CSRF)攻击。

正是在这样的背景下,JWT应运而生,提供了一种无状态、可扩展的认证方案。

2. JWT核心概念与结构

JWT,全称JSON Web Token,是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息。这些信息可以被验证和信任,因为它们是经过数字签名的。

一个JWT通常由三部分组成,它们之间用点(.)分隔,形成一个紧凑的字符串:Header.Payload.Signature

2.1 Header(头部)

Header部分通常包含两类信息:

  • typ (Type):表示令牌的类型,通常为JWT
  • alg (Algorithm):表示签名JWT所使用的算法,例如HMAC SHA256(HS256)或RSA(RS256)。

一个典型的Header示例如下:

json 复制代码
{
  "alg": "HS256",
  "typ": "JWT"
}

这个JSON对象随后会经过Base64Url编码,形成JWT的第一部分。

2.2 Payload(载荷)

Payload部分是JWT的核心,用于存放实际需要传递的数据,这些数据被称为"声明"(Claims)。声明分为三类:

  1. Registered Claims(注册声明) :这些是预定义的一些声明,非强制使用,但推荐使用,以提供互操作性。常见的注册声明包括:

    • iss (Issuer):签发人
    • exp (Expiration Time):过期时间戳,JWT的过期时间
    • sub (Subject):主题,通常是用户ID
    • aud (Audience):受众,指定JWT的接收方
    • nbf (Not Before):生效时间,JWT在此时间之前是无效的
    • iat (Issued At):签发时间,JWT的签发时间
    • jti (JWT ID):JWT的唯一标识符,用于防止重放攻击
  2. Public Claims(公共声明) :可以由JWT的使用者自定义,但为了避免冲突,应该在IANA JSON Web Token Registry中注册,或者定义为包含碰撞抵抗命名空间的URI。

  3. Private Claims(私有声明) :这些是为特定应用或双方之间共享信息而创建的自定义声明。它们既不是注册声明,也不是公共声明,因此需要确保接收方和发送方都理解这些声明的含义。

一个典型的Payload示例如下:

json 复制代码
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "exp": 1516242622 // 过期时间,例如1小时后
}

Payload同样会经过Base64Url编码,形成JWT的第二部分。

2.3 Signature(签名)

Signature部分用于验证JWT的完整性,确保JWT在传输过程中没有被篡改。它的生成方式取决于Header中指定的alg算法。

如果algHS256(HMAC SHA256),签名过程如下:

scss 复制代码
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

其中:

  • base64UrlEncode(header):Base64Url编码后的Header。
  • base64UrlEncode(payload):Base64Url编码后的Payload。
  • secret:服务器端持有的一个密钥(secret key)。这个密钥是JWT安全性的核心,绝不能泄露给客户端

签名过程的本质是:将编码后的Header和Payload连接起来,然后使用指定的算法和密钥进行哈希运算。生成的哈希值就是Signature。

2.4 完整的JWT字符串

将Header、Payload和Signature三部分用点(.)连接起来,就得到了最终的JWT字符串,例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjQyNjIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

这个字符串就是客户端在后续请求中需要携带的身份凭证。

3. JWT的工作原理与认证流程

JWT的认证流程通常遵循以下步骤:

  1. 用户登录:客户端(浏览器或移动应用)向认证服务器发送用户的登录凭据(用户名和密码)。

  2. 服务器验证:认证服务器接收到凭据后,验证用户身份。如果验证成功,服务器会生成一个JWT。

    • 服务器根据用户ID、角色等信息构建Payload。
    • 选择签名算法和密钥(secret)。
    • 使用Header、Payload和Secret生成Signature。
    • 将三部分组合成完整的JWT字符串。
  3. 返回JWT :认证服务器将生成的JWT返回给客户端。通常,JWT会放在HTTP响应体中,或者作为HTTP响应头的一部分(例如Authorization头)。

  4. 客户端存储JWT:客户端接收到JWT后,将其存储在本地。常见的存储位置包括:

    • LocalStorage/SessionStorage:简单方便,但存在XSS攻击风险。
    • Cookie :可以设置HttpOnlySecure属性来增强安全性,但仍可能面临CSRF风险。
  5. 后续请求携带JWT :客户端在后续访问受保护资源的请求中,将JWT放置在HTTP请求头中发送给资源服务器。最常见的做法是使用Authorization头,格式为Authorization: Bearer <token>

    vbnet 复制代码
    GET /api/protected-resource
    Authorization: Bearer eyJhbGciOiJIUzI1Ni...<your_jwt_token>...
  6. 资源服务器验证JWT :资源服务器接收到请求后,从Authorization头中提取JWT,并进行验证:

    • 检查签名:使用相同的签名算法和密钥,重新计算JWT的签名。如果计算出的签名与JWT中携带的签名不一致,则说明JWT被篡改,验证失败。
    • 检查过期时间 :验证Payload中的exp声明,确保JWT未过期。
    • 检查其他声明 :根据业务需求,验证issaud等其他声明。
  7. 授权访问:如果JWT验证通过,资源服务器认为请求合法,并根据JWT中包含的用户信息(如用户ID、角色等)进行授权,然后返回相应的资源。

4. JWT的安全性考量:Secret与加盐

JWT的安全性很大程度上依赖于secret密钥的保密性。一旦secret泄露,攻击者就可以伪造有效的JWT,从而绕过认证机制。

4.1 Secret密钥的重要性

  • 保密性secret必须是高度机密的,只能由签发JWT的服务器持有,绝不能暴露在客户端代码中或通过不安全的渠道传输。
  • 复杂性secret应该足够长且随机,难以被暴力破解。建议使用至少32个字符的随机字符串。
  • 管理secret不应硬编码在代码中,而应通过环境变量、配置文件或密钥管理服务进行安全存储和管理。

4.2 JWT与"加盐"(Salt)

在密码学中,"加盐"通常用于增强密码哈希的安全性,防止彩虹表攻击。它是在哈希密码之前,向密码添加一个随机字符串。

JWT本身不直接"加盐" 。JWT的签名过程使用的是一个固定的secret密钥,而不是为每个用户生成一个唯一的盐。JWT的"自包含"特性意味着它不依赖于服务器端存储任何用户特定的状态信息(如Session ID)。

然而,secret密钥的作用类似于一个全局的"盐",它确保了即使Payload内容相同,只要secret不同,生成的签名也会不同。如果需要为每个用户提供更强的安全性,例如在Payload中包含敏感信息,可以考虑对Payload进行加密,但这会增加JWT的复杂性和处理开销。

总结来说:

  • JWT的签名依赖于一个强大的、保密的secret密钥。
  • secret密钥是JWT安全性的基石,其重要性不亚于传统认证中的Session ID。
  • JWT不使用传统意义上的"加盐"来保护Payload内容,但其签名机制本身提供了完整性保护。

5. 总结(上篇)

本文作为深入理解JWT认证系列的第一部分,我们详细探讨了JWT的起源背景、核心结构(Header、Payload、Signature)以及其无状态的工作原理。我们了解到,JWT通过将用户信息和元数据编码并签名,形成一个自包含的令牌,从而解决了传统Session-Cookie机制在分布式、跨域等场景下的痛点。同时,我们也强调了secret密钥在JWT安全性中的核心地位。在下一篇文章中,我们将进一步探讨如何在前端项目中集成JWT,包括状态管理、路由守卫以及与后端API的交互,并分享一些实践中的最佳策略。

相关推荐
在逃的吗喽26 分钟前
黑马头条项目详解
前端·javascript·ajax
袁煦丞34 分钟前
有Nextcloud家庭共享不求人:cpolar内网穿透实验室第471个成功挑战
前端·程序员·远程工作
小磊哥er1 小时前
【前端工程化】前端项目开发过程中如何做好通知管理?
前端
拾光拾趣录1 小时前
一次“秒开”变成“转菊花”的线上事故
前端
你我约定有三1 小时前
前端笔记:同源策略、跨域问题
前端·笔记
JHCan3331 小时前
一个没有手动加分号引发的bug
前端·javascript·bug
pe7er1 小时前
懒人的代码片段
前端
没有bug.的程序员1 小时前
《 Spring Boot启动流程图解:自动配置的真相》
前端·spring boot·自动配置·流程图
拾光拾趣录2 小时前
一次诡异的登录失效
前端·浏览器
拾光拾趣录2 小时前
一张 8K 海报差点把首屏拖垮
前端·性能优化