文章目录
好的,我们来详细、清晰地介绍一下JWT(JSON Web Token)的鉴权流程和其背后的原理。这是一个在现代Web应用和API安全中非常核心的概念。
我将用一个通俗的比喻来帮助你理解:
- 传统的Session认证:就像你去一个游乐场,在入口处把你的随身物品(用户信息)存放在一个储物柜里(服务器Session存储),然后游乐场给你一个手环/储物牌(Session ID)。之后你玩任何项目,都需要出示这个手环,工作人员(服务器)看到手环后,会去储物柜区域找到你的柜子,确认你的信息。特点是服务器需要维护大量的储物柜。
- JWT认证:就像你拿到了一张带有防伪签名的“贵宾通行证”。这张通行证本身就写清楚了你的信息(你是谁、有什么权限、有效期到何时),并且上面有主办方(服务器)的特殊防伪签名。你玩任何项目,只需出示这张通行证,工作人员一看,确认签名是真的,信息没被篡改,就直接让你通过。特点是工作人员(服务器)无需再去查阅档案或储物柜。
1. JWT的原理:它是什么构成的?
JWT的本质是一个经过签名的、自包含的字符串。它由三部分组成,每个部分都使用Base64Url编码,并用点(.
)连接起来。
xxxxx.yyyyy.zzzzz
(Header . Payload . Signature)
a) 第一部分:Header (头部)
Header部分是一个JSON对象,通常包含两部分信息:
typ
(Type): 令牌的类型,固定为 “JWT”。alg
(Algorithm): 使用的签名算法,例如HS256
(HMAC SHA256) 或RS256
(RSA)。
示例JSON:
{
"alg": "HS256",
"typ": "JWT"
}
这部分JSON会经过Base64Url编码,形成JWT的第一部分。
b) 第二部分:Payload (载荷)
Payload部分也是一个JSON对象,这里存放了所有需要传递的实际数据,这些数据被称为“声明 (Claims)”。声明分为三类:
- Registered Claims (标准中注册的声明):这些是官方建议使用的预定义声明,以提供一组通用的、可互操作的声明。例如:
iss
(Issuer): 签发者sub
(Subject): 主题(通常是用户的ID)aud
(Audience): 接收者exp
(Expiration Time): 过期时间戳。这是非常重要的一个声明,用于保证令牌在一定时间后失效。iat
(Issued At): 签发时间戳
- Public Claims (公共的声明):可以由使用者自由定义,但为了避免冲突,应该在IANA JSON Web Token Registry中注册。
- Private Claims (私有的声明):这是我们最常使用的,由通信双方自由定义的声明。例如,你可以放入用户的ID、角色、用户名等自定义信息。
示例JSON:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1719768799
}
这部分JSON也会经过Base64Url编码,形成JWT的第二部分。
⚠️ 重要提醒:Payload部分仅仅是经过了Base64Url编码,它没有被加密! 这意味着任何人都可以解码并读取其中的内容。因此,绝对不要在Payload中存放任何敏感信息,如密码。
c) 第三部分:Signature (签名)
Signature是JWT最核心的安全保障。它的生成过程如下:
- 将经过Base64Url编码的Header和Payload用点(
.
)连接起来,形成一个字符串。 - 使用在Header中定义的签名算法(如HS256),配合一个只有服务器知道的密钥(secret),对这个字符串进行加密签名。
伪代码示例:
Signature = HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
your_secret_key
)
签名的作用:
- 验证身份:确保令牌是由可信任的服务器签发的(因为只有服务器知道密钥)。
- 防止篡改:如果有人试图修改Header或Payload的内容,由于他们没有密钥,无法生成正确的签名。服务器在验证时会发现签名不匹配,从而拒绝该令牌。
2. JWT的鉴权流程
下面是JWT在实际应用中的完整流程:
-
用户登录:用户使用用户名和密码发起登录请求。
-
服务器验证与签发Token:
- 服务器接收到请求,验证用户名和密码是否正确。
- 验证通过后,服务器根据用户ID、角色等信息,结合过期时间等,生成Payload。
- 服务器使用保存在服务端的密钥(secret),对Header和Payload进行签名,生成一个完整的JWT。
-
服务器返回Token:服务器将生成的JWT字符串作为响应返回给客户端。
-
客户端存储Token:
- 客户端(通常是浏览器或App)在收到JWT后,需要将其存储起来。
- 常见存储位置包括:浏览器的
localStorage
、sessionStorage
或HttpOnly Cookie
。
-
客户端携带Token发起请求:
- 此后,客户端向服务器发起的每一个需要鉴权的API请求,都必须在HTTP请求头中携带这个JWT。
- 最规范的方式是放在
Authorization
请求头中,并使用Bearer
方案:Authorization: Bearer <your_jwt_token>
-
服务器验证Token:
- 服务器的API端点在收到请求后,会有一个**中间件(Middleware)**来专门处理鉴权。
- 中间件会从
Authorization
请求头中提取JWT。 - 服务器不需要查询数据库,而是执行以下验证步骤:
a. 验证签名:服务器取出请求中的Header和Payload,用自己存储的同一个密钥(secret),按照同样的算法重新计算一次签名。然后,将计算出的新签名与JWT中自带的签名进行比对。
* 如果一致,说明令牌有效且未被篡改。
* 如果不一致,说明令牌是伪造的或被篡改过,认证失败。
b. 验证声明:签名验证通过后,服务器还会检查Payload中的声明,例如,检查exp
声明,确认令牌是否已经过期。 - 所有验证都通过后,服务器就认为该请求是合法的,可以信任Payload中的用户信息(如用户ID),并继续处理该请求。如果验证失败,则返回
401 Unauthorized
错误。
3. JWT的优缺点
优点:
- 无状态与可扩展性:服务器端无需存储任何Session信息。这使得服务器可以轻松地水平扩展(增加更多服务器实例),而不用担心Session同步问题。非常适合微服务架构。
- 自包含:令牌本身包含了所有需要的信息,可以减少因查询用户信息而对数据库造成的压力。
- 跨域友好:可以轻松地用于跨域的API调用,因为JWT是通过HTTP头发送的,不受浏览器同源策略的限制。
- 多端适用:非常适合Web、移动App、桌面应用等多种客户端。
缺点:
- 无法主动失效:一旦一个JWT被签发,在它到期之前,它就一直是有效的。如果用户在此期间想要“注销登录”,服务器无法主动让这个令牌失效。常见的解决方案是:
- 设置较短的过期时间,并配合“刷新令牌(Refresh Token)”机制。
- 维护一个“黑名单”,将需要失效的令牌ID存入Redis等缓存中。每次验证时先查黑名单。但这又让服务变得“有状态”了,违背了JWT的初衷。
- 体积较大:相比于一个简单的Session ID,JWT的体积要大得多,会增加每次HTTP请求的开销。
- 安全性:签名的密钥必须被妥善保管,一旦泄露,攻击者就可以签发任意的、合法的令牌。