MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JWT的有效期和续签机制探讨

2023-06-044.3k 阅读

JWT 基础概念

JSON Web Token(JWT)是一种用于在网络应用中安全传输信息的开放标准(RFC 7519)。它通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

头部(Header)

头部通常由两部分组成:令牌的类型(即 JWT)和使用的哈希算法,如 HMAC SHA256 或 RSA。以下是一个 JSON 格式的头部示例:

{
  "alg": "HS256",
  "typ": "JWT"
}

然后将这个 JSON 对象进行 Base64Url 编码,就形成了 JWT 的第一部分。

载荷(Payload)

载荷是 JWT 的第二部分,它包含声明(claims),也就是关于实体(通常是用户)和其他数据的陈述。例如:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

这里的 “sub” 代表主题(subject),“name” 是用户名字,“iat” 表示签发时间(issued at)。同样,这个 JSON 对象也会进行 Base64Url 编码,形成 JWT 的第二部分。

签名(Signature)

要创建签名部分,需要使用编码后的头部、编码后的载荷、一个密钥(secret)和头部中指定的签名算法。例如,如果使用 HMAC SHA256 算法,签名将按如下方式创建:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

签名用于验证消息在传输过程中没有被更改,并且在使用私钥签名的情况下,还可以验证 JWT 的发送者的身份。

JWT 的有效期

为什么需要有效期

JWT 的有效期是保障系统安全的重要机制。它确保了令牌不会被无限期使用,降低了令牌被盗用后造成长期安全风险的可能性。例如,用户在一段时间不活动后,其持有的 JWT 过期,此时若恶意用户获取到该过期令牌,也无法利用其访问受保护资源。

有效期相关字段

在 JWT 的载荷中,有几个与有效期相关的标准字段:

  • exp(expiration time):过期时间,是一个 Unix 时间戳,表示 JWT 何时过期。例如:
{
  "exp": 1516239022
}

当 JWT 被验证时,验证逻辑会检查当前时间是否超过了 “exp” 字段指定的时间。如果超过,则该 JWT 被视为无效。

  • nbf(not before):生效时间,表示 JWT 在什么时间之前是无效的。例如:
{
  "nbf": 1516238722
}

如果当前时间早于 “nbf” 字段指定的时间,JWT 同样被视为无效。

  • iat(issued at):签发时间,记录 JWT 是什么时候签发的。虽然它本身不直接决定 JWT 的有效期,但在某些场景下可用于计算令牌已生效的时长。例如:
{
  "iat": 1516238422
}

代码示例:设置和验证有效期

以下以 Python 的 PyJWT 库为例,展示如何在生成 JWT 时设置有效期以及验证 JWT 的有效期。

首先安装 PyJWT

pip install PyJWT

生成带有有效期的 JWT:

import jwt
import datetime

# 密钥
SECRET_KEY = "your_secret_key"

# 载荷数据
payload = {
    "sub": "1234567890",
    "name": "John Doe",
    "iat": datetime.datetime.utcnow(),
    "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}

# 生成 JWT
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print(token)

验证 JWT 的有效期:

import jwt
import datetime

# 密钥
SECRET_KEY = "your_secret_key"

try:
    # 假设这是接收到的 JWT
    token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNjI2ODg5OTY0LCJleHAiOjE2MjY4OTAxODR9.652e5d5c6d7a8d9d0f1e2a3b4c5d6e7f8"
    payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    print(payload)
except jwt.ExpiredSignatureError:
    print("JWT 已过期")
except jwt.InvalidTokenError:
    print("无效的 JWT")

JWT 续签机制

为什么需要续签机制

虽然 JWT 的有效期有助于提高安全性,但在一些场景下,频繁地让用户重新登录会降低用户体验。例如,用户在使用一个长时间运行的应用程序,如在线文档编辑工具时,如果每隔一段时间就需要重新登录,会非常不方便。续签机制可以在不影响安全性的前提下,延长用户的登录状态。

续签机制的实现方式

  1. 基于前端的续签:前端在检测到 JWT 即将过期时,向后端发送续签请求。后端验证请求的合法性后,生成新的 JWT 并返回给前端。这种方式的优点是后端压力较小,因为续签的时机由前端控制。但缺点是前端需要复杂的逻辑来检测 JWT 的过期时间,并及时发起续签请求。

  2. 基于后端的续签:后端在每次接收到 JWT 验证请求时,检查 JWT 的剩余有效期。如果剩余有效期低于某个阈值,后端自动生成新的 JWT 并返回给前端。这种方式的优点是后端对整个过程有更好的控制,且前端逻辑相对简单。但缺点是会增加后端的计算压力,因为每次请求都需要检查和处理续签逻辑。

代码示例:基于后端的续签机制

以 Python Flask 应用为例,展示如何实现基于后端的 JWT 续签机制。

首先安装所需库:

pip install flask PyJWT

Flask 应用代码如下:

from flask import Flask, request, jsonify
import jwt
import datetime

app = Flask(__name__)
SECRET_KEY = "your_secret_key"

# 模拟用户数据
users = {
    "user1": "password1"
}

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')

    if username in users and users[username] == password:
        payload = {
            "sub": username,
            "iat": datetime.datetime.utcnow(),
            "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
        }
        token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
        return jsonify({"token": token}), 200
    else:
        return jsonify({"message": "Invalid credentials"}), 401

@app.route('/protected', methods=['GET'])
def protected():
    token = request.headers.get('Authorization')
    if not token:
        return jsonify({"message": "Token is missing"}), 401

    try:
        token = token.replace('Bearer ', '')
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        # 检查是否需要续签
        current_time = datetime.datetime.utcnow()
        expiration_time = datetime.datetime.fromtimestamp(payload['exp'])
        if (expiration_time - current_time) < datetime.timedelta(minutes=5):
            new_payload = {
                "sub": payload['sub'],
                "iat": current_time,
                "exp": current_time + datetime.timedelta(minutes=30)
            }
            new_token = jwt.encode(new_payload, SECRET_KEY, algorithm="HS256")
            return jsonify({"message": "Resource accessed successfully", "new_token": new_token}), 200
        else:
            return jsonify({"message": "Resource accessed successfully"}), 200
    except jwt.ExpiredSignatureError:
        return jsonify({"message": "JWT has expired"}), 401
    except jwt.InvalidTokenError:
        return jsonify({"message": "Invalid JWT"}), 401

if __name__ == '__main__':
    app.run(debug=True)

在上述代码中,/login 路由用于用户登录并获取初始 JWT。/protected 路由是一个受保护的资源,每次请求时会检查 JWT 的有效性。如果 JWT 剩余有效期小于 5 分钟,会生成新的 JWT 并返回给前端。

JWT 有效期和续签机制的安全考量

有效期设置的安全权衡

较短的有效期虽然能提高安全性,减少令牌被盗用后的风险窗口,但频繁的过期会导致用户体验下降,特别是在用户需要长时间保持登录状态的应用中。而较长的有效期虽然能提升用户体验,但增加了令牌被盗用后恶意使用的风险。因此,需要根据应用的安全需求和用户场景来合理设置有效期。例如,对于金融应用,安全要求较高,有效期可以设置得相对较短;而对于一些一般性的资讯类应用,有效期可以适当延长。

续签机制的安全风险

  1. 续签请求的安全传输:无论是前端发起还是后端自动续签,续签请求本身必须通过安全的通道传输,如 HTTPS。否则,续签请求可能被拦截,恶意用户可以利用拦截到的请求获取新的 JWT,从而延长其非法访问权限。

  2. 防止重放攻击:在续签过程中,要防止重放攻击。例如,后端可以使用一次性令牌(Nonce)或者时间戳等机制,确保续签请求是新鲜的,而不是被恶意重放的旧请求。

  3. 密钥管理:续签过程同样依赖于密钥的安全性。如果密钥泄露,恶意用户可以随意生成或续签 JWT,导致整个安全机制失效。因此,密钥需要妥善保管,定期更换,并采用安全的存储方式,如硬件安全模块(HSM)。

结合其他安全机制

  1. 多因素认证(MFA):可以将 JWT 的有效期和续签机制与多因素认证相结合。例如,在续签时要求用户提供额外的认证因素,如短信验证码或指纹识别,进一步提高安全性。
  2. IP 地址绑定:将 JWT 与用户的 IP 地址绑定,在续签时检查请求的 IP 地址是否与首次登录时的 IP 地址一致。如果不一致,可以要求用户重新进行完整的认证流程。

不同应用场景下的有效期和续签策略

单页应用(SPA)

在单页应用中,用户通常会长时间与应用交互。可以设置相对较长的 JWT 有效期,如 1 - 2 小时。对于续签机制,可以采用前端检测即将过期并发起续签请求的方式。前端可以在页面加载或定时任务中检查 JWT 的有效期,当剩余有效期低于 10 - 15 分钟时,向后端发送续签请求。这样可以在保证用户体验的同时,降低安全风险。

移动应用

移动应用的使用场景较为复杂,用户可能随时打开或关闭应用。可以设置中等长度的有效期,如 30 分钟 - 1 小时。对于续签机制,可以结合应用的活跃状态检测。当应用处于前台且用户有操作时,视为活跃状态。如果检测到 JWT 即将过期且应用处于活跃状态,由前端发起续签请求。若应用处于后台或长时间无操作,用户再次打开应用时,可要求重新登录或进行更严格的认证,如指纹识别或密码验证后再续签 JWT。

企业级应用

企业级应用通常对安全性要求较高,同时可能涉及到不同权限级别的用户。对于普通员工用户,可以设置较短的有效期,如 15 - 30 分钟,并采用后端自动续签机制。每次请求时,后端检查有效期,若剩余时间低于 5 分钟则自动续签。对于高权限用户,如管理员,除了较短的有效期设置外,还可以在续签时要求进行额外的身份验证,如二次密码输入或使用企业内部的认证设备。

性能优化

减少验证次数

在一些场景下,可以采用缓存机制来减少 JWT 的验证次数。例如,对于一些只读的受保护资源,在一定时间内(如几分钟),如果已经验证过 JWT 的有效性,可以直接从缓存中获取资源,而不需要再次验证 JWT。这样可以减轻后端服务器的计算压力,提高响应速度。

续签优化

对于基于后端的续签机制,可以在生成新的 JWT 时进行优化。例如,复用部分原 JWT 的载荷数据,减少新 JWT 的生成时间。同时,合理设置续签的阈值,避免频繁续签导致的性能问题。如果阈值设置过低,可能会导致每次请求都触发续签,增加后端负载;如果阈值设置过高,又可能无法及时续签,影响用户体验。

跨域问题

在涉及跨域请求的场景中,JWT 的有效期和续签机制可能会受到影响。例如,不同域名之间的 Cookie 无法共享,这可能导致前端在处理 JWT 时出现问题。可以通过设置合适的 CORS(跨域资源共享)策略来解决这个问题。在服务器端,配置允许跨域请求,并确保在响应头中正确设置相关的 CORS 字段,如 Access - Control - Allow - OriginAccess - Control - Allow - Headers 等,以便前端能够正确处理 JWT 及其续签请求。

总结 JWT 有效期和续签机制的最佳实践

  1. 合理设置有效期:根据应用的安全需求和用户场景,权衡安全性和用户体验,设置合适的 JWT 有效期。
  2. 选择合适的续签机制:根据应用的架构和性能要求,选择基于前端或后端的续签机制,或者结合两者的优点进行实现。
  3. 确保安全传输:无论是 JWT 本身还是续签请求,都要通过安全的通道传输,如 HTTPS。
  4. 防止重放攻击:在续签过程中,采用合适的机制防止重放攻击,如使用一次性令牌或时间戳。
  5. 结合其他安全机制:将 JWT 的有效期和续签机制与多因素认证、IP 地址绑定等其他安全机制相结合,提高整体安全性。
  6. 优化性能:通过缓存机制和合理设置续签阈值等方式,优化 JWT 验证和续签过程的性能。
  7. 处理跨域问题:在跨域场景中,正确配置 CORS 策略,确保 JWT 及其续签请求能够正常处理。

通过以上对 JWT 有效期和续签机制的深入探讨,开发者可以根据不同的应用需求,设计出安全、高效且用户体验良好的认证机制。在实际应用中,还需要不断关注安全态势,及时调整和优化相关策略,以应对不断变化的安全威胁。