1 初识JWT
JWT(JSON Web Token)的出现主要是为了解决分布式系统中身份认证和权限授权的问题,特别是在跨域、前后端分离的架构中。以下是其核心目标和解决的痛点:
一、传统session认证的局限性
在JWT之前,Web应用常用Session-Cookie机制进行身份认证:
- 服务器状态存储:Session数据存储在服务器端(如内存、Redis),客户端通过Cookie中的Session ID访问。这导致:
- 可扩展性差:分布式系统中需要Session共享(如粘性会话、分布式缓存)。
- 单点故障:依赖中心化的Session存储。
- 跨域支持弱:Cookie受同源策略限制,难以直接用于跨域请求(如微服务、第三方API)。
- CSRF风险:基于Cookie的认证易受跨站请求伪造(CSRF)攻击。
- 移动端适配困难:原生App、小程序等非浏览器环境对Cookie的支持有限。
二、JWT解决的核心问题
1. 无状态认证
JWT将用户信息编码在Token中,服务器无需存储Session数据。每次请求携带完整信息,实现无状态:
- 优点:
- 简化分布式系统架构(无需Session共享)。
- 支持水平扩展(任意服务器均可验证Token)。
- 适合微服务、API网关等场景。
2. 跨域与跨服务调用
JWT通过HTTP Header(如Authorization: Bearer Token)传递,不依赖Cookie,天然支持:
- 跨域资源共享(CORS)。
- 第三方服务集成(如OAuth2.0 + JWT)。
- 前后端分离架构(前端存储Token,后端无状态验证)。
3. 安全性提升
- 防CSRF:不依赖Cookie,避免CSRF攻击。
- 完整性验证:通过签名确保Token未被篡改。
- 细粒度权限控制:可在Payload中嵌入用户角色、权限等信息。
4. 多平台兼容
支持任何能存储和发送字符串的客户端:
- 浏览器(LocalStorage/Cookie)。
- 移动应用(原生存储)。
- IoT设备。
5. 去中心化信任
JWT的签名机制允许不同服务独立验证Token有效性,无需依赖中心化认证服务(如公钥加密)。例如:
- 服务A生成Token,服务B/C通过公钥直接验证。
- 适合OAuth2.0中的JWT令牌场景。
三、典型应用场景
- 单点登录(SSO):一个Token在多个子系统中通用。
- API授权:第三方应用通过JWT访问资源服务器。
- 信息交换:安全地在不同服务间传递用户信息(如用户ID、角色)。
- 一次性操作:如邮件验证链接、重置密码Token(通过设置短有效期)。
四、对比其他技术
| 技术 | 存储位置 | 状态性 | 跨域支持 | 典型场景 |
|---|---|---|---|---|
| Session-Cookie | 服务器+客户端 | 有状态 | 差 | 传统Web应用 |
| JWT | 客户端 | 无状态 | 好 | 前后端分离、微服务 |
| OAuth2.0 | 客户端+授权服务器 | 混合 | 好 | 第三方登录、API授权 |
五、JWT的局限性
虽然JWT解决了许多问题,但也有适用边界:
- Token体积大:包含完整用户信息,可能影响性能。
- 无法主动失效:Token一旦签发,直到过期前都有效(需配合黑名单或短有效期)。
- 敏感信息风险:Payload是Base64编码而非加密,不要存储密码等敏感数据。
总结
JWT的核心价值在于用无状态、自包含的方式安全传递身份信息,尤其适合分布式、微服务和移动应用场景。它通过牺牲部分灵活性(如无法动态修改权限)换取了架构的可扩展性和简单性。
2 熟悉JWT
一、JWT基本概念
JWT(JSON Web Token)是一种用于在网络应用间安全传递声明的开放标准(RFC 7519)。它通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature),形式为Header.Payload.Signature。
- 头部(Header):包含两部分信息,令牌类型(通常是JWT)和使用的签名算法(如HMAC SHA256或RSA)。
- 载荷(Payload):包含声明(Claims),即关于实体(通常是用户)和其他数据的声明。声明分为三种类型:
- 注册声明:如iss(发行人)、sub(主题)、aud(受众)等。
- 公开声明:由各方自由定义。
- 私有声明:在同意使用的一方之间定义的自定义声明。
- 签名(Signature):为了创建签名部分,需要使用编码后的Header、编码后的Payload、一个秘钥(secret)和Header中指定的签名算法。签名用于验证消息在传递过程中没有被更改,并且在使用私钥签名的情况下,还可以验证JWT的发送者身份。
二、JWT的原理
JWT的核心原理是通过数字签名确保信息的完整性和真实性。当用户登录成功后,服务器生成一个JWT并返回给客户端,客户端在后续请求中携带这个JWT,服务器通过验证签名来确认请求的合法性。整个流程如下:
- 用户认证:用户向服务器提供身份凭证(如用户名和密码)。
- 生成JWT:服务器验证凭证后,创建一个包含用户信息的JWT,并使用私钥签名。
- 返回JWT:服务器将JWT返回给客户端,通常存储在LocalStorage或Cookie中。
- 携带JWT:客户端在后续请求中,将JWT放在HTTP请求头的
Authorization字段中,格式为Bearer {token}。 - 验证JWT:服务器接收请求后,验证JWT的签名和有效性,确认用户身份。
- 授权访问:服务器验证通过后,根据JWT中的用户信息授权用户访问受保护的资源。
三、JWT实例
以下是一个JWT的示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
将其分解为三部分:
- 头部(Header):
{
"alg": "HS256",
"typ": "JWT"
}
- 载荷(Payload):
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
- 签名(Signature):
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
四、JWT的使用实例
以下是一个使用Python和Flask实现JWT认证的示例:
from flask import Flask, request, jsonify
import jwt
from datetime import datetime, timedelta
from functools import wraps
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key' # 用于签名的秘钥
# 生成JWT
def generate_token(user_id):
payload = {
'user_id': user_id,
'exp': datetime.utcnow() + timedelta(hours=1) # 令牌有效期1小时
}
token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')
return token
# 验证JWT
def decode_token(token):
try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
return 'Token expired'
except jwt.InvalidTokenError:
return 'Invalid token'
# JWT认证装饰器
def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = None
# 从请求头中获取token
if 'Authorization' in request.headers:
auth_header = request.headers['Authorization']
if auth_header.startswith('Bearer '):
token = auth_header.split(' ')[1]
if not token:
return jsonify({'message': 'Token missing!'}), 401
# 验证token
data = decode_token(token)
if isinstance(data, str): # 如果返回的是错误信息
return jsonify({'message': data}), 401
# 将用户ID添加到请求中
request.user_id = data['user_id']
return f(*args, **kwargs)
return decorated
# 登录接口 - 生成JWT
@app.route('/login', methods=['POST'])
def login():
# 这里应该验证用户的用户名和密码
# 为简化示例,直接生成token
user_id = 123
token = generate_token(user_id)
return jsonify({'token': token})
# 受保护的资源接口
@app.route('/protected', methods=['GET'])
@token_required
def protected():
return jsonify({'message': f'Protected resource accessed by user {request.user_id}'})
if __name__ == '__main__':
app.run(debug=True)
下面是一个使用java-jwt库的JWT实现示例,包含生成和验证JWT的基本功能:
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
public class JwtExample {
// 密钥,应妥善保管,不建议硬编码在代码中
private static final String SECRET = "your-256-bit-secret";
// 发行人
private static final String ISSUER = "example.com";
// 过期时间(毫秒),这里设置为1小时
private static final long EXPIRATION_TIME = 1000 * 60 * 60;
public static void main(String[] args) {
// 生成JWT
String token = generateToken("user123");
System.out.println("生成的JWT: " + token);
// 验证JWT
try {
DecodedJWT decodedJWT = verifyToken(token);
System.out.println("验证成功!");
System.out.println("用户ID: " + decodedJWT.getSubject());
System.out.println("发行人: " + decodedJWT.getIssuer());
System.out.println("签发时间: " + decodedJWT.getIssuedAt());
System.out.println("过期时间: " + decodedJWT.getExpiresAt());
} catch (JWTVerificationException e) {
System.out.println("验证失败: " + e.getMessage());
}
}
/**
* 生成JWT
* @param userId 用户ID
* @return JWT字符串
*/
public static String generateToken(String userId) {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
return JWT.create()
.withIssuer(ISSUER)
.withSubject(userId)
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(algorithm);
} catch (Exception e) {
System.out.println("生成JWT失败: " + e.getMessage());
return null;
}
}
/**
* 验证JWT
* @param token JWT字符串
* @return 解码后的JWT
* @throws JWTVerificationException 验证失败时抛出异常
*/
public static DecodedJWT verifyToken(String token) throws JWTVerificationException {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
return verifier.verify(token);
}
}
使用说明:
- 需要添加java-jwt依赖(Maven坐标):
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
- 代码功能说明:
generateToken方法:创建包含用户ID的JWT,设置发行人、签发时间和过期时间verifyToken方法:验证JWT签名和声明字段的有效性main方法:演示生成和验证流程
- 注意事项:
- 密钥SECRET应存储在安全位置,如环境变量或配置文件
- 实际应用中建议添加更多声明字段(如角色、权限)
- 过期时间可根据业务需求调整
- 可根据需要添加自定义签名算法和声明验证逻辑
五、使用JWT需要注意的问题
-
安全存储JWT:
- 避免将JWT存储在localStorage中,因为容易受到XSS攻击。
- 考虑使用HttpOnly Cookie存储JWT,防止脚本访问。
- 对于SPA应用,可考虑使用短期JWT配合刷新令牌(Refresh Token)。
-
防止CSRF攻击:
- 如果使用Cookie存储JWT,需采取措施防止CSRF攻击,如使用SameSite属性。
-
签名密钥安全:
- 确保签名密钥不泄露,特别是使用HMAC算法时。
- 考虑使用非对称加密(如RSA),公钥用于验证签名,私钥安全存储在服务器端。
-
令牌过期策略:
- 设置合理的过期时间,平衡安全性和用户体验。
- 考虑实现刷新令牌机制,减少用户重新登录的频率。
-
避免在JWT中存储敏感信息:
- 虽然JWT是加密的,但不是不可解密的,不要在JWT中存储敏感信息(如密码)。
-
验证完整性:
- 始终验证JWT的签名和有效期。
- 检查JWT中的声明(如iss、aud、exp等)是否符合预期。
-
注销处理:
- JWT是无状态的,无法直接注销。可实现黑名单机制或缩短令牌有效期。
-
性能考虑:
- JWT可能会比较大,特别是包含大量声明时,注意避免影响网络性能。
-
防范重放攻击:
- 考虑在JWT中添加唯一标识符(jti),并结合服务器端的黑名单机制。
-
更新权限问题:
- JWT在生成后权限信息就固定了,如需动态更新权限,需重新生成JWT或采用其他机制。
通过合理使用JWT并注意上述问题,可以构建安全、高效的身份验证和授权系统。

2万+

被折叠的 条评论
为什么被折叠?



