斗地主计分器JS实现:状态机驱动的规则建模与防错设计
2026/10/17 5:49:22
在当今的API安全领域,JSON Web Token(JWT)正逐渐成为身份认证的主流方案。与传统的API Key相比,JWT提供了更高级别的安全性保障,特别适合需要精细控制访问权限的场景。
传统API Key的主要问题在于:
JWT认证的核心优势体现在:
# 传统API Key调用示例(不推荐) curl -H "X-QW-Api-Key: ABCD1234EFGH" \ 'https://api.qweather.com/v7/weather/now?location=101010100' # JWT认证调用示例(推荐) curl -H "Authorization: Bearer eyJhbGciOiJFZERTQS..." \ 'https://api.qweather.com/v7/weather/now?location=101010100'Ed25519是目前最先进的椭圆曲线数字签名算法之一,相比传统RSA具有以下优势:
| 特性 | Ed25519 | RSA-2048 |
|---|---|---|
| 签名速度 | 快3-4倍 | 基准值 |
| 验证速度 | 快5-6倍 | 基准值 |
| 密钥长度 | 256位 | 2048位 |
| 安全性 | 128位 | 112位 |
生成密钥对的具体步骤:
确保系统已安装OpenSSL 3.0+版本
# 检查OpenSSL版本 openssl version生成Ed25519私钥
openssl genpkey -algorithm ED25519 -out private.pem从私钥导出公钥
openssl pkey -pubout -in private.pem -out public.pem重要提示:私钥文件(private.pem)必须严格保密,建议设置400权限并存储在安全位置。公钥文件(public.pem)需要上传到和风天气控制台。
常见问题排查:
winget install OpenSSL.OpenSSL完成密钥生成后,需要将公钥配置到和风天气控制台:
配置完成后,系统会生成一个凭据ID(kid),这个ID需要记录并在后续生成JWT时使用。
验证公钥是否匹配的方法:
# 计算本地公钥SHA256 openssl dgst -sha256 public.pem # 与控制台显示的SHA256值比对一个标准的JWT由三部分组成,通过点号(.)连接:header.payload.signature
必须包含算法类型和凭据ID:
{ "alg": "EdDSA", "kid": "T8GYP76CD2" }核心字段包括:
sub: 项目ID(控制台获取)iat: 签发时间(建议设置为当前时间-30秒)exp: 过期时间(最长不超过24小时)示例Payload:
{ "sub": "29KVA6G27T", "iat": 1703912400, "exp": 1703916000 }签名是JWT安全性的核心,生成步骤:
Base64URL与标准Base64的区别:替换
+/为-_并去掉末尾的=
import jwt import time private_key = """-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIMt6oO4GC+QnzZFEp/Q245fpquD+j5wKApSJaY2MHFuJ -----END PRIVATE KEY-----""" headers = {"alg": "EdDSA", "kid": "T8GYP76CD2"} payload = { "sub": "29KVA6G27T", "iat": int(time.time()) - 30, "exp": int(time.time()) + 3600 } token = jwt.encode(payload, private_key, algorithm="EdDSA", headers=headers) print(f"JWT: {token}")const { SignJWT, importPKCS8 } = require('jose'); async function generateJWT() { const privateKey = await importPKCS8( `-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIMt6oO4GC+QnzZFEp/Q245fpquD+j5wKApSJaY2MHFuJ -----END PRIVATE KEY-----`, 'EdDSA' ); const jwt = await new SignJWT({ sub: '29KVA6G27T' }) .setProtectedHeader({ alg: 'EdDSA', kid: 'T8GYP76CD2' }) .setIssuedAt(Math.floor(Date.now() / 1000) - 30) .setExpirationTime('1h') .sign(privateKey); console.log(`JWT: ${jwt}`); }import java.security.*; import java.util.*; import io.jsonwebtoken.*; public class JwtGenerator { public static void main(String[] args) { String privateKey = "-----BEGIN PRIVATE KEY-----\n" + "MC4CAQAwBQYDK2VwBCIEIMt6oO4GC+QnzZFEp/Q245fpquD+j5wKApSJaY2MHFuJ\n" + "-----END PRIVATE KEY-----"; long iat = System.currentTimeMillis() / 1000 - 30; long exp = iat + 3600; String jwt = Jwts.builder() .setHeaderParam("alg", "EdDSA") .setHeaderParam("kid", "T8GYP76CD2") .claim("sub", "29KVA6G27T") .claim("iat", iat) .claim("exp", exp) .signWith(Keys.hmacShaKeyFor(privateKey.getBytes())) .compact(); System.out.println("JWT: " + jwt); } }import requests api_host = "https://api.qweather.com" endpoint = "/geo/v2/city/lookup" params = {"location": "北京"} response = requests.get(f"{api_host}{endpoint}", headers={"Authorization": f"Bearer {token}"}, params=params) location_id = response.json()["location"][0]["id"]endpoint = "/v7/weather/now" params = { "location": location_id, "lang": "zh", "unit": "m" } response = requests.get(f"{api_host}{endpoint}", headers={"Authorization": f"Bearer {token}"}, params=params) weather_data = response.json() print(f"当前温度: {weather_data['now']['temp']}°C")典型成功响应:
{ "code": "200", "updateTime": "2025-07-17T17:18+08:00", "now": { "temp": "35", "feelsLike": "36", "text": "晴", "windDir": "东北风", "windScale": "2" } }常见错误代码:
401: JWT认证失败(检查过期时间/kid/sub)404: 接口路径错误(检查API Host和endpoint)429: 请求频率超限(检查调用频率)JWT缓存策略:
密钥轮换方案:
性能优化建议:
# 使用会话保持连接池 session = requests.Session() session.headers.update({"Authorization": f"Bearer {token}"}) # 启用gzip压缩 session.headers.update({"Accept-Encoding": "gzip"})安全防护措施:
在实际项目中,我曾遇到时区问题导致JWT立即过期的情况。解决方案是在生成iat和exp时明确指定时区,确保服务器和客户端使用相同的时区标准。