摘要
在现代Web应用中,用户认证和授权是不可或缺的核心功能。随着前后端分离和分布式系统的普及,传统的Session-Cookie认证机制面临着跨域、扩展性等挑战。JSON Web Token(JWT)作为一种轻量级、自包含的开放标准,正逐渐成为主流的认证解决方案。本文将以掘金博主的视角,深入剖析JWT的底层原理、核心结构、工作流程以及安全性考量,旨在帮助开发者全面理解JWT的精髓,为构建安全、高效的Web应用奠定基础。
1. 引言:无状态HTTP与认证的挑战
HTTP协议是无状态的,这意味着服务器在处理两次独立的请求时,无法直接识别它们是否来自同一个用户。在用户认证场景中,这带来了巨大的挑战:用户登录后,如何在后续的每次请求中都证明其身份,而无需重复提交用户名和密码?
传统的解决方案是基于Session-Cookie机制:
- 登录:用户提交用户名和密码到服务器。
- 创建Session:服务器验证凭据后,创建一个Session(会话),并将Session ID存储在服务器内存或数据库中。
- 设置Cookie:服务器将Session ID通过Cookie发送给客户端。
- 后续请求:客户端在后续的每次请求中,都会自动携带这个Cookie(包含Session ID)。
- 验证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)。声明分为三类:
-
Registered Claims(注册声明) :这些是预定义的一些声明,非强制使用,但推荐使用,以提供互操作性。常见的注册声明包括:
iss
(Issuer):签发人exp
(Expiration Time):过期时间戳,JWT的过期时间sub
(Subject):主题,通常是用户IDaud
(Audience):受众,指定JWT的接收方nbf
(Not Before):生效时间,JWT在此时间之前是无效的iat
(Issued At):签发时间,JWT的签发时间jti
(JWT ID):JWT的唯一标识符,用于防止重放攻击
-
Public Claims(公共声明) :可以由JWT的使用者自定义,但为了避免冲突,应该在IANA JSON Web Token Registry中注册,或者定义为包含碰撞抵抗命名空间的URI。
-
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
算法。
如果alg
是HS256
(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的认证流程通常遵循以下步骤:
-
用户登录:客户端(浏览器或移动应用)向认证服务器发送用户的登录凭据(用户名和密码)。
-
服务器验证:认证服务器接收到凭据后,验证用户身份。如果验证成功,服务器会生成一个JWT。
- 服务器根据用户ID、角色等信息构建Payload。
- 选择签名算法和密钥(secret)。
- 使用Header、Payload和Secret生成Signature。
- 将三部分组合成完整的JWT字符串。
-
返回JWT :认证服务器将生成的JWT返回给客户端。通常,JWT会放在HTTP响应体中,或者作为HTTP响应头的一部分(例如
Authorization
头)。 -
客户端存储JWT:客户端接收到JWT后,将其存储在本地。常见的存储位置包括:
- LocalStorage/SessionStorage:简单方便,但存在XSS攻击风险。
- Cookie :可以设置
HttpOnly
和Secure
属性来增强安全性,但仍可能面临CSRF风险。
-
后续请求携带JWT :客户端在后续访问受保护资源的请求中,将JWT放置在HTTP请求头中发送给资源服务器。最常见的做法是使用
Authorization
头,格式为Authorization: Bearer <token>
。vbnetGET /api/protected-resource Authorization: Bearer eyJhbGciOiJIUzI1Ni...<your_jwt_token>...
-
资源服务器验证JWT :资源服务器接收到请求后,从
Authorization
头中提取JWT,并进行验证:- 检查签名:使用相同的签名算法和密钥,重新计算JWT的签名。如果计算出的签名与JWT中携带的签名不一致,则说明JWT被篡改,验证失败。
- 检查过期时间 :验证Payload中的
exp
声明,确保JWT未过期。 - 检查其他声明 :根据业务需求,验证
iss
、aud
等其他声明。
-
授权访问:如果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的交互,并分享一些实践中的最佳策略。