三分钟带你认识JWT (Java Web Token)

1 初识JWT

JWT(JSON Web Token)的出现主要是为了解决分布式系统身份认证权限授权的问题,特别是在跨域前后端分离的架构中。以下是其核心目标和解决的痛点:

一、传统session认证的局限性

在JWT之前,Web应用常用Session-Cookie机制进行身份认证:

  1. 服务器状态存储:Session数据存储在服务器端(如内存、Redis),客户端通过Cookie中的Session ID访问。这导致:
    • 可扩展性差:分布式系统中需要Session共享(如粘性会话、分布式缓存)。
    • 单点故障:依赖中心化的Session存储。
  2. 跨域支持弱:Cookie受同源策略限制,难以直接用于跨域请求(如微服务、第三方API)。
  3. CSRF风险:基于Cookie的认证易受跨站请求伪造(CSRF)攻击。
  4. 移动端适配困难:原生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令牌场景。

三、典型应用场景

  1. 单点登录(SSO):一个Token在多个子系统中通用。
  2. API授权:第三方应用通过JWT访问资源服务器。
  3. 信息交换:安全地在不同服务间传递用户信息(如用户ID、角色)。
  4. 一次性操作:如邮件验证链接、重置密码Token(通过设置短有效期)。

四、对比其他技术

技术存储位置状态性跨域支持典型场景
Session-Cookie服务器+客户端有状态传统Web应用
JWT客户端无状态前后端分离、微服务
OAuth2.0客户端+授权服务器混合第三方登录、API授权

五、JWT的局限性

虽然JWT解决了许多问题,但也有适用边界:

  1. Token体积大:包含完整用户信息,可能影响性能。
  2. 无法主动失效:Token一旦签发,直到过期前都有效(需配合黑名单或短有效期)。
  3. 敏感信息风险: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,服务器通过验证签名来确认请求的合法性。整个流程如下:

  1. 用户认证:用户向服务器提供身份凭证(如用户名和密码)。
  2. 生成JWT:服务器验证凭证后,创建一个包含用户信息的JWT,并使用私钥签名。
  3. 返回JWT:服务器将JWT返回给客户端,通常存储在LocalStorage或Cookie中。
  4. 携带JWT:客户端在后续请求中,将JWT放在HTTP请求头的Authorization字段中,格式为Bearer {token}
  5. 验证JWT:服务器接收请求后,验证JWT的签名和有效性,确认用户身份。
  6. 授权访问:服务器验证通过后,根据JWT中的用户信息授权用户访问受保护的资源。

三、JWT实例

以下是一个JWT的示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

将其分解为三部分:

  1. 头部(Header)
{
  "alg": "HS256",
  "typ": "JWT"
}
  1. 载荷(Payload)
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
  1. 签名(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);
    }
}    

使用说明:

  1. 需要添加java-jwt依赖(Maven坐标):
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>
  1. 代码功能说明:
  • generateToken方法:创建包含用户ID的JWT,设置发行人、签发时间和过期时间
  • verifyToken方法:验证JWT签名和声明字段的有效性
  • main方法:演示生成和验证流程
  1. 注意事项:
  • 密钥SECRET应存储在安全位置,如环境变量或配置文件
  • 实际应用中建议添加更多声明字段(如角色、权限)
  • 过期时间可根据业务需求调整
  • 可根据需要添加自定义签名算法和声明验证逻辑

五、使用JWT需要注意的问题

  1. 安全存储JWT

    • 避免将JWT存储在localStorage中,因为容易受到XSS攻击。
    • 考虑使用HttpOnly Cookie存储JWT,防止脚本访问。
    • 对于SPA应用,可考虑使用短期JWT配合刷新令牌(Refresh Token)。
  2. 防止CSRF攻击

    • 如果使用Cookie存储JWT,需采取措施防止CSRF攻击,如使用SameSite属性。
  3. 签名密钥安全

    • 确保签名密钥不泄露,特别是使用HMAC算法时。
    • 考虑使用非对称加密(如RSA),公钥用于验证签名,私钥安全存储在服务器端。
  4. 令牌过期策略

    • 设置合理的过期时间,平衡安全性和用户体验。
    • 考虑实现刷新令牌机制,减少用户重新登录的频率。
  5. 避免在JWT中存储敏感信息

    • 虽然JWT是加密的,但不是不可解密的,不要在JWT中存储敏感信息(如密码)。
  6. 验证完整性

    • 始终验证JWT的签名和有效期。
    • 检查JWT中的声明(如iss、aud、exp等)是否符合预期。
  7. 注销处理

    • JWT是无状态的,无法直接注销。可实现黑名单机制或缩短令牌有效期。
  8. 性能考虑

    • JWT可能会比较大,特别是包含大量声明时,注意避免影响网络性能。
  9. 防范重放攻击

    • 考虑在JWT中添加唯一标识符(jti),并结合服务器端的黑名单机制。
  10. 更新权限问题

    • JWT在生成后权限信息就固定了,如需动态更新权限,需重新生成JWT或采用其他机制。

通过合理使用JWT并注意上述问题,可以构建安全、高效的身份验证和授权系统。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值