JWT Token 完全解析:从基础到高级应用

早些年在做RPA项目时,就用到了JWT Token,当时它已经作为一门新的技术点。当时引入该项技术,也是传统 Session 认证已经有它明显的局限性。

传统Session认证的局限:

传统Session认证用户登录的场景大多这样处理:

1)服务器创建一个唯一的 session ID 并存储在服务器端(如内存、数据库或文件系统);

2)session ID 通过 cookie 返回给客户端;

3)后续请求中,客户端通过 cookie 发送 session ID;

4)服务器验证 session ID 并查找对应的 session 数据

这样的处理方式,确实在系统需要分布式拓展时就会遇到很多问题,如:分布式系统中,session 数据需要在多个服务器间共享,会增加架构复杂度;另外大规模应用中,大量 session 数据会占用服务器内存资源;还有如果 session 存储服务器故障,可能影响整个系统的认证功能;当然最熟悉的场景就是跨域:浏览器 cookie 遵循同源策略,不同域名之间无法共享 cookie;对于CORS的配置,都需要在服务端配置复杂的 CORS 头,其实增加安全风险,还有安全方面而言:基于 cookie 的认证是很容易受到 CSRF(跨站请求伪造)攻击。

当然随着移动互联网的到来,还存在这样的一个问题:移动应用兼容性太差,我们会遇到如下问题:原生应用不支持 cookie:移动应用(iOS/Android)没有浏览器 cookie 机制,需要额外实现;状态管理困难:原生应用与 Web 应用的状态管理方式不一致,增加开发复杂度;

当然安全性问题也是需要重点考虑的,攻击者可以通过诱使用户使用特定 session ID 登录,还有我们常说的会话劫持(Session Hijacking):如果 cookie 被窃取(如通过 XSS 攻击),攻击者可以冒充用户;再加上CSRF 防护复杂:需要额外实现 CSRF 令牌机制,增加开发难度。

然JWT的出现解决了以上描述的这些问题,先科普一下相关知识。主要目的是了解该技术与传统方式的不同。

一、什么是 JWT Token?

1、 起源与背景

JSON Web Token(JWT)是一种基于 JSON 的开放标准(RFC 7519),用于在网络应用间安全地传输声明(claims)。它由互联网工程任务组(IETF)在 2015 年提出,旨在解决传统 session 认证的局限性,特别是在跨域和移动应用场景下。

2、核心概念

JWT 本质上是一个字符串,由三部分组成:

  • Header(头部):通常包含令牌类型(如 "JWT")和使用的签名算法(如 HMAC SHA256 或 RSA)
  • Payload(负载):包含声明(claims),即关于实体(通常是用户)和其他数据的声明
  • Signature(签名):用于验证消息在传输过程中没有被更改,并且在使用私钥签名的情况下,还可以验证 JWT 的发送者身份

二、JWT 的工作原理

接下来,我们在此拓展开来,更加细化的描述一下传统认证和JWT的对比。传统Session的认证,我在开篇已经描述了一下,还是在这再次述出来,主要是为了对比:

1、传统认证与 JWT 的对比

传统的基于 session 的认证流程:

  1. 用户登录后,服务器创建 session 并存储在服务器端
  2. 返回 session ID 给客户端(通常通过 cookie)
  3. 后续请求中,客户端发送 session ID
  4. 服务器验证 session ID 并查找对应的 session 数据

这种方式的缺点:

  • 服务器需要存储大量 session 数据,扩展性差
  • 跨域请求时存在 cookie 限制
  • 移动端对 cookie 支持不佳

JWT 认证流程:

  1. 用户登录后,服务器生成 JWT 并返回给客户端
  2. 客户端在后续请求中将 JWT 包含在请求头中(通常是 Authorization 字段)
  3. 服务器验证 JWT 的签名和有效性
  4. 直接从 JWT 中获取用户信息,无需查询数据库

为了更好的演示,我用python语法进行一下描述

2、JWT 的生成与验证

下面是一个使用 Python 和 PyJWT 库生成和上述描述的验证 JWT 的过程:

import jwt
from datetime import datetime, timedelta

# 生成JWT
def generate_token(user_id, secret_key, expires_in=3600):
    # 设置过期时间
    expiration = datetime.utcnow() + timedelta(seconds=expires_in)
    
    # 构建payload
    payload = {
        'user_id': user_id,
        'exp': expiration,
        'iat': datetime.utcnow(),
        'iss': 'your_application',  # 发行人
        'aud': 'your_client'  # 受众
    }
    
    # 生成JWT
    token = jwt.encode(
        payload,
        secret_key,
        algorithm='HS256'
    )
    
    return token

# 验证JWT
def verify_token(token, secret_key):
    try:
        # 验证并解码JWT
        payload = jwt.decode(
            token,
            secret_key,
            algorithms=['HS256'],
            audience='your_client',
            issuer='your_application'
        )
        return payload
    except jwt.ExpiredSignatureError:
        print('Token已过期')
        return None
    except jwt.InvalidAudienceError:
        print('受众无效')
        return None
    except jwt.InvalidIssuerError:
        print('发行人无效')
        return None
    except jwt.InvalidTokenError:
        print('无效的Token')
        return None

# 使用示例
if __name__ == '__main__':
    secret_key = 'your_super_secret_key_here'
    user_id = 12345
    
    # 生成Token
    token = generate_token(user_id, secret_key)
    print(f"生成的JWT: {token}")
    
    # 验证Token
    payload = verify_token(token, secret_key)
    if payload:
        print(f"验证成功,用户ID: {payload['user_id']}")

3、JWT 的结构解析

让我们分解一个实际的 JWT 示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NSwiZXhwIjoxNjkyMjY4ODAwLCJpYXQiOjE2OTIyNjUyMDAsImlzcyI6InlvdXJfYXBwbGljYXRpb24iLCJhdWQiOiJ5b3VyX2NsaWVudCJ9.-KX2h4p1dXJnJ2oK8eX8dK2bX5eK7o8zJ3dW5cQ7e8Y

这个 JWT 可以分为三部分:

  1. Header(Base64 编码)

    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
  2. Payload(Base64 编码)

    {
      "user_id": 12345,
      "exp": 1692268800,
      "iat": 1692265200,
      "iss": "your_application",
      "aud": "your_client"
    }
    
  3. Signature

    -KX2h4p1dXJnJ2oK8eX8dK2bX5eK7o8zJ3dW5cQ7e8Y
    

签名的计算方式:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

通过以上方式我们可以明显的发现JWT的优势:

三、JWT 的优势与适用场景

1、主要优势

  1. 无状态:JWT 包含了所有必要信息,服务器不需要存储 session 数据
  2. 跨域支持:可以通过 localStorage 或 cookie 存储,并在请求头中发送
  3. 安全性:使用签名验证,防止篡改;支持加密
  4. 扩展性:可以包含丰富的用户信息,减少数据库查询
  5. 多平台兼容:适用于 Web、移动应用和 API

2、适用场景

  1. 身份验证:用户登录后返回 JWT,后续请求中验证
  2. 信息交换:安全地在各方之间传输信息
  3. 单点登录(SSO):多个服务可以共享同一个 JWT
  4. 微服务架构:在不同服务之间传递用户身份和权限

接下来,我们就用这四个场景用传统session和jwt来对比一下:

四、具体场景对比

1、跨域 API 调用
  • 传统 session

    • 需要在 API 服务器配置复杂的 CORS 头
    • 客户端需要处理 cookie 的跨域问题
    • 可能遇到浏览器的预检请求(Preflight Request)性能问题
  • JWT

    • 客户端直接在请求头中包含 JWT(如Authorization: Bearer <token>
    • API 服务器只需验证 JWT 签名,无需处理 cookie
    • 简化 CORS 配置,通常只需允许Authorization
2、微服务架构
  • 传统 session

    • 多个服务之间需要共享 session 数据
    • 需要统一的 session 存储服务(如 Redis 集群)
    • 服务间调用需要传递 session 信息
  • JWT

    • 每个服务独立验证 JWT,无需共享 session
    • 用户信息直接包含在 JWT 中,服务间调用无需额外查询
    • 可以实现细粒度的权限控制(如在 JWT 中包含用户角色或权限列表)
3、移动应用认证
  • 传统 session

    • 需要在移动应用中模拟 cookie 机制
    • 处理 session 过期和刷新逻辑复杂
    • 难以与 Web 应用保持一致的认证体验
  • JWT

    • 移动应用可以轻松存储和管理 JWT
    • 与 Web 应用使用相同的认证流程
    • 可以实现自动刷新机制(如使用刷新令牌)

五、JWT 在跨域和移动场景下的最佳实践

1、跨域安全
  • 使用 HTTPS:确保 JWT 在传输过程中不被拦截
  • 自定义请求头:使用Authorization头而非 cookie 存储 JWT
  • 验证 issuer 和 audience:在 JWT 验证时,确保令牌是发给当前服务的
  • 限制 Token 作用域:为不同类型的请求颁发不同的 JWT(如访问令牌和刷新令牌)
2、 移动应用安全
  • 安全存储:在移动设备上使用安全存储机制存储 JWT
  • 生物识别增强:结合设备生物识别(如指纹、面部识别)增强安全性
  • 令牌刷新机制:实现刷新令牌机制,避免频繁登录
  • 设备绑定:在 JWT 中包含设备标识,检测异常登录
3、性能优化
  • Token 压缩:避免在 JWT 中包含过多不必要的信息
  • 缓存验证结果:对于频繁访问的资源,缓存 JWT 验证结果
  • 使用短期令牌:减少令牌被盗用的风险,同时通过刷新令牌保持用户会话

下面结合我项目的经历,说说JWT的高级用法

六、JWT 的高级用法

1、 刷新 Token 机制

JWT 的一个缺点是一旦签发,直到过期前都有效,这可能带来安全风险。刷新 Token 机制可以解决这个问题:

import jwt
from datetime import datetime, timedelta
import secrets

# 生成访问Token和刷新Token
def generate_tokens(user_id, secret_key, refresh_secret, access_expires=3600, refresh_expires=86400):
    # 生成访问Token
    access_payload = {
        'user_id': user_id,
        'exp': datetime.utcnow() + timedelta(seconds=access_expires),
        'iat': datetime.utcnow(),
        'type': 'access'
    }
    access_token = jwt.encode(access_payload, secret_key, algorithm='HS256')
    
    # 生成刷新Token(使用不同的密钥和更长的过期时间)
    refresh_payload = {
        'user_id': user_id,
        'exp': datetime.utcnow() + timedelta(seconds=refresh_expires),
        'iat': datetime.utcnow(),
        'type': 'refresh',
        'jti': secrets.token_hex(16)  # 唯一标识符,用于撤销
    }
    refresh_token = jwt.encode(refresh_payload, refresh_secret, algorithm='HS256')
    
    return {
        'access_token': access_token,
        'refresh_token': refresh_token
    }

# 刷新访问Token
def refresh_access_token(refresh_token, secret_key, refresh_secret):
    try:
        # 验证刷新Token
        refresh_payload = jwt.decode(
            refresh_token,
            refresh_secret,
            algorithms=['HS256']
        )
        
        # 检查Token类型
        if refresh_payload.get('type') != 'refresh':
            raise jwt.InvalidTokenError('Invalid token type')
        
        # 生成新的访问Token
        access_payload = {
            'user_id': refresh_payload['user_id'],
            'exp': datetime.utcnow() + timedelta(seconds=3600),
            'iat': datetime.utcnow(),
            'type': 'access'
        }
        access_token = jwt.encode(access_payload, secret_key, algorithm='HS256')
        
        return access_token
    except jwt.ExpiredSignatureError:
        print('刷新Token已过期,请重新登录')
        return None
    except jwt.InvalidTokenError:
        print('无效的刷新Token')
        return None

# 使用示例
if __name__ == '__main__':
    secret_key = 'your_access_token_secret'
    refresh_secret = 'your_refresh_token_secret'
    user_id = 12345
    
    # 生成Token对
    tokens = generate_tokens(user_id, secret_key, refresh_secret)
    print(f"访问Token: {tokens['access_token']}")
    print(f"刷新Token: {tokens['refresh_token']}")
    
    # 模拟刷新Token
    new_access_token = refresh_access_token(tokens['refresh_token'], secret_key, refresh_secret)
    if new_access_token:
        print(f"刷新后的访问Token: {new_access_token}")

2、基于角色的访问控制(RBAC)

在 JWT 中包含用户角色信息,实现细粒度的访问控制:

# 生成包含角色信息的JWT
def generate_token_with_roles(user_id, roles, secret_key, expires_in=3600):
    payload = {
        'user_id': user_id,
        'roles': roles,  # 例如: ['admin', 'editor']
        'exp': datetime.utcnow() + timedelta(seconds=expires_in),
        'iat': datetime.utcnow()
    }
    
    return jwt.encode(payload, secret_key, algorithm='HS256')

# 验证并检查角色权限
def verify_and_check_role(token, secret_key, required_role):
    try:
        payload = jwt.decode(token, secret_key, algorithms=['HS256'])
        
        # 检查用户是否有所需角色
        if 'roles' in payload and required_role in payload['roles']:
            return True
        else:
            return False
    except jwt.ExpiredSignatureError:
        return False
    except jwt.InvalidTokenError:
        return False

# Flask API中使用角色验证的示例
from flask import Flask, request, jsonify

app = Flask(__name__)
SECRET_KEY = 'your_secret_key'

@app.route('/admin/dashboard')
def admin_dashboard():
    token = request.headers.get('Authorization')
    if not token:
        return jsonify({'message': 'Token missing'}), 401
    
    token = token.replace('Bearer ', '')
    
    # 检查用户是否有admin角色
    if not verify_and_check_role(token, SECRET_KEY, 'admin'):
        return jsonify({'message': 'Permission denied'}), 403
    
    return jsonify({'message': 'Welcome to admin dashboard'})

3、多因素认证集成

将 JWT 与多因素认证(MFA)结合:

import pyotp

# 生成TOTP密钥和URL
def generate_mfa_secret(username):
    secret = pyotp.random_base32()
    totp = pyotp.TOTP(secret)
    provisioning_url = totp.provisioning_uri(
        username,
        issuer_name="Your Application"
    )
    return {
        'secret': secret,
        'qr_code_url': provisioning_url
    }

# 验证MFA代码
def verify_mfa_code(secret, code):
    totp = pyotp.TOTP(secret)
    return totp.verify(code)

# 结合MFA的JWT生成
def generate_token_with_mfa(user_id, mfa_secret, mfa_code, secret_key):
    # 验证MFA代码
    if not verify_mfa_code(mfa_secret, mfa_code):
        raise ValueError('Invalid MFA code')
    
    # 生成包含MFA验证标记的JWT
    payload = {
        'user_id': user_id,
        'mfa_verified': True,
        'exp': datetime.utcnow() + timedelta(minutes=30),
        'iat': datetime.utcnow()
    }
    
    return jwt.encode(payload, secret_key, algorithm='HS256')

4、与 OAuth 2.0 集成

JWT 可以作为 OAuth 2.0 的访问令牌(Access Token):

from authlib.integrations.flask_client import OAuth

# 配置OAuth客户端
oauth = OAuth()

# 注册OAuth提供者
oauth.register(
    name='your_oauth_provider',
    client_id='your_client_id',
    client_secret='your_client_secret',
    access_token_url='https://provider.com/oauth/token',
    authorize_url='https://provider.com/oauth/authorize',
    api_base_url='https://provider.com/api/',
    client_kwargs={'scope': 'openid email profile'}
)

# 处理OAuth回调
@app.route('/auth/callback')
def callback():
    # 获取访问令牌(可能是JWT)
    token = oauth.your_oauth_provider.authorize_access_token()
    
    # 如果返回的是JWT,可以直接解析
    if 'id_token' in token:
        # 验证并解析JWT
        try:
            id_token = jwt.decode(
                token['id_token'],
                verify=False,  # 在实际应用中应该验证签名
                algorithms=['RS256']
            )
            # 处理用户信息
            user_id = id_token.get('sub')
            email = id_token.get('email')
            
            # 生成自己的应用JWT
            app_token = generate_token(user_id, app.config['SECRET_KEY'])
            
            return jsonify({
                'message': 'Login successful',
                'token': app_token,
                'user': {
                    'id': user_id,
                    'email': email
                }
            })
        except jwt.InvalidTokenError:
            return jsonify({'message': 'Invalid token'}), 401
    else:
        return jsonify({'message': 'No ID token received'}), 400

以下是我总结的JWT的安全实践的场景

六、JWT 的安全最佳实践

1、密钥管理

  1. 使用足够长且随机的密钥
  2. 对不同类型的 Token 使用不同的密钥(如访问 Token 和刷新 Token)
  3. 定期轮换密钥
  4. 不要在代码中硬编码密钥,使用环境变量或密钥管理服务

2、Token 存储

  1. 在浏览器中,使用 HttpOnly cookie 存储 JWT,防止 XSS 攻击
  2. 避免使用 localStorage 或 sessionStorage 存储 JWT
  3. 在移动应用中,使用安全的存储机制(如 iOS 的 Keychain 或 Android 的 Keystore)

3、防止 CSRF 攻击

  1. 如果使用 cookie 存储 JWT,确保设置 SameSite 属性
  2. 对于敏感操作,额外验证 CSRF 令牌

4、 其他安全措施

  1. 使用 HTTPS 确保 Token 在传输过程中的安全
  2. 设置合理的 Token 过期时间
  3. 实现 Token 撤销机制(如黑名单或刷新 Token)
  4. 对敏感信息进行加密,而不仅仅是签名
  5. 监控 Token 使用情况,检测异常活动

当然说了这么多,JWT也有其局限性,但这个局限性完全可以用其他方案组合解决问题。

七、JWT 的局限性与替代方案

1、主要局限性

  1. Token 体积:JWT 可能会比较大,特别是包含大量 claims 时
  2. 不可撤销性:一旦签发,直到过期前都有效(除非实现额外的撤销机制)
  3. 安全性依赖密钥:如果密钥泄露,整个系统将面临风险
  4. 性能开销:每次请求都需要验证签名,可能影响性能

2、替代方案

  1. OAuth 2.0:更适合授权场景,特别是第三方应用访问资源
  2. SAML:企业级单点登录解决方案
  3. Session Cookies:适合传统 Web 应用,不需要跨域支持
  4. PASETO:比 JWT 更安全的替代方案,解决了一些 JWT 的设计问题

最后小结

JWT 是一种强大的身份验证和信息交换机制,特别适合现代分布式系统和移动应用。它通过数字签名保证了数据的完整性和安全性,同时提供了良好的扩展性和跨平台支持。在使用 JWT 时,需要注意安全最佳实践,特别是密钥管理、Token 存储和防止常见的安全漏洞。对于复杂场景,可以结合刷新 Token、RBAC、MFA 等高级技术,进一步提升系统的安全性和用户体验。

尽管 JWT 有一些局限性,但在大多数场景下,它仍然是身份验证和授权的首选解决方案。随着技术的发展,我们也看到了一些改进 JWT 的新方案出现,如 PASETO,这些都值得我们进一步关注和探索。让我们拭目以待吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值