Web Crypto 安全认证实战:从 JWT 签名到端到端加密

3841 字
19 分钟
Web Crypto 安全认证实战:从 JWT 签名到端到端加密

登录按钮背后的请求里,藏着整个 Web 安全最复杂的部分之一。

2026 年的今天,前端几乎从不「裸跑」:登录拿到 JWT、Token 存到内存、用 Authorization: Bearer 调 API、刷新 Token 时跑 OAuth2 PKCE、敏感场景还要端到端加密。这一切的底层,几乎都绕不开浏览器原生提供的 crypto.subtle

但很多前端工程师对它的理解停留在「MD5 一下」「AES 加个密」。结果就是:JWT 在前端用 localStorage 一存,被 XSS 一锅端;端到端加密方案自己拍脑袋设计,IV 重用、密钥派生参数过小,最后等于没加密。

这篇文章不讲 crypto-js,不讲第三方 SDK,只用浏览器原生的 Web Crypto API。从最基础的 HMAC 验签,到 JWT 完整性保护,到 ECDH 端到端密钥交换,再到完整的「客户端 → 服务端 → 客户端」安全通道设计。每一步都配可运行代码 + 攻击模型分析 + 生产级参数表。

一、为什么是 Web Crypto API,不是 crypto-js?#

先回答一个常被忽略的问题:Node.js 端有 crypto 模块,为什么浏览器里就不能用 require('crypto')

答案是沙箱安全。浏览器拒绝给 JS 任何「真随机数生成器 + 大数运算 + 加密原语」的访问权限,否则恶意脚本可以在后台暴力破解用户密码。Web Crypto API 是浏览器唯一的加密接口,且只在安全上下文(HTTPS、localhost)中可用。

维度Web Crypto APIcrypto-js / jsencrypt
实现层浏览器原生(C/C++ + 硬件加速)纯 JavaScript
性能(1MB AES)~5ms~80ms
认证FIPS 140-2(部分浏览器)
抗侧信道浏览器进程隔离完全暴露在 JS 层
异步 API强制 async同步(阻塞主线程)
体积0 KB~200KB
真随机数crypto.getRandomValues(CSPRNG)需 polyfill(Math.random 不安全)

结论:生产环境不要用任何 JS 加密库。Web Crypto API 是唯一选择。

二、HMAC 签名:JWT 的心脏#

JWT(JSON Web Token)的结构是 base64url(header).base64url(payload).signature。其中 signature 就是用 HMAC(哈希消息认证码)对前两段做的对称签名

2.1 手动签一个 JWT#

/**
* 将 ArrayBuffer / Uint8Array 转为 base64url
*/
function bufToB64Url(buf) {
const bytes = new Uint8Array(buf);
let bin = '';
for (let i = 0; i < bytes.byteLength; i++) bin += String.fromCharCode(bytes[i]);
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function strToB64Url(str) {
return bufToB64Url(new TextEncoder().encode(str));
}
/**
* 用 HMAC-SHA256 签名 JWT
* @param {object} payload - 要签名的数据
* @param {string} secret - 服务端密钥(前端不能拥有!但演示用)
* @returns {string} JWT
*/
async function signJWT(payload, secret) {
const header = { alg: 'HS256', typ: 'JWT' };
const h = strToB64Url(JSON.stringify(header));
const p = strToB64Url(JSON.stringify(payload));
// 关键步骤:HMAC-SHA256
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign', 'verify']
);
const sigBuf = await crypto.subtle.sign(
'HMAC',
key,
new TextEncoder().encode(`${h}.${p}`)
);
return `${h}.${p}.${bufToB64Url(sigBuf)}`;
}
// === 使用示例 ===
const token = await signJWT(
{ userId: 12345, role: 'admin', exp: Math.floor(Date.now() / 1000) + 3600 },
'my-super-secret-key-at-least-32-bytes'
);
console.log(token);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMzQ1LCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3NTBfQ...

2.2 验签:前端能做吗?#

技术上能,权限上不该能。如果前端能验签,意味着前端拥有密钥,攻击者只要拿到前端 JS 就能伪造 Token。

但下面这种场景是合理的:前端验签的密钥 ≠ 服务端签发的密钥。例如公开的 JWK(公钥)用于验签,私钥永远只留在服务端(即 RS256 模式)。

/**
* 用公钥验签 JWT(RS256)
* @param {string} token - JWT
* @param {string} publicKeyPem - PEM 格式的公钥
* @returns {Promise<object|null>} payload 或 null
*/
async function verifyRS256(token, publicKeyPem) {
const [h, p, s] = token.split('.');
if (!h || !p || !s) return null;
// 把 PEM 头尾去掉,base64 decode,再包成 ArrayBuffer
const pemBody = publicKeyPem
.replace(/-----BEGIN PUBLIC KEY-----/, '')
.replace(/-----END PUBLIC KEY-----/, '')
.replace(/\s+/g, '');
const der = Uint8Array.from(atob(pemBody), c => c.charCodeAt(0));
const key = await crypto.subtle.importKey(
'spki',
der,
{ name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
false,
['verify']
);
const sig = Uint8Array.from(
s.replace(/-/g, '+').replace(/_/g, '/') + '=='.slice(0, (4 - s.length % 4) % 4),
c => c.charCodeAt(0)
);
const ok = await crypto.subtle.verify(
{ name: 'RSASSA-PKCS1-v1_5' },
key,
sig,
new TextEncoder().encode(`${h}.${p}`)
);
if (!ok) return null;
const payload = JSON.parse(atob(p.replace(/-/g, '+').replace(/_/g, '/')));
// 还要校验 exp
if (payload.exp && payload.exp * 1000 < Date.now()) return null;
return payload;
}

关键原则:HS256 用对称密钥(签发方和验证方共享)→ 不能在前端。RS256 / ES256 用非对称密钥(私钥签发、公钥验证)→ 公钥可以放前端

2.3 算法选择:HS256 vs RS256 vs ES256#

算法类型签名长度验签速度推荐场景
HS256对称32B极快服务端单点(不推荐前后端分离)
RS256RSA 非对称256B较慢传统企业,对兼容性要求高
ES256椭圆曲线64B现代 Web 应用首选
EdDSAEd2551964B极快新项目,性能敏感

ES256 签名长度只有 RS256 的 1/4,验签速度 5-10 倍,密钥也短(256 位),是当前最推荐的 JWT 算法。

三、密码哈希:从 PBKDF2 到 Argon2#

永远不要在前端「加密」密码。前端能做的是:用慢哈希函数派生出密钥,做端到端加密。但要让前端登录用慢哈希,又会泄露用户密码长度信息。

3.1 PBKDF2:Web Crypto 自带的方案#

/**
* 用密码派生 AES 密钥
* @param {string} password - 用户密码
* @param {Uint8Array} salt - 盐(每个用户唯一)
* @param {number} iterations - 迭代次数(越大越慢)
*/
async function deriveKey(password, salt, iterations = 600_000) {
const baseKey = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(password),
'PBKDF2',
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations,
hash: 'SHA-256',
},
baseKey,
{ name: 'AES-GCM', length: 256 },
false, // 不可导出
['encrypt', 'decrypt']
);
}
// 使用:派生 AES 密钥
const salt = crypto.getRandomValues(new Uint8Array(16));
const aesKey = await deriveKey('user-input-password', salt, 600_000);

3.2 迭代次数怎么定?#

OWASP 2026 推荐:PBKDF2-SHA256 ≥ 600,000 次。在 2020 年这个数字还是 310,000。原因是硬件变快了。

测试你的设备能跑多快:

async function benchmarkPBKDF2(targetMs = 250) {
const password = 'benchmark-password';
const salt = crypto.getRandomValues(new Uint8Array(16));
const baseKey = await crypto.subtle.importKey(
'raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits']
);
let iterations = 100_000;
while (iterations <= 2_000_000) {
const t0 = performance.now();
await crypto.subtle.deriveBits(
{ name: 'PBKDF2', salt, iterations, hash: 'SHA-256' },
baseKey,
256
);
const t1 = performance.now();
if (t1 - t0 > targetMs) return iterations;
iterations = Math.floor(iterations * 1.5);
}
return iterations;
}
const optimal = await benchmarkPBKDF2(250);
console.log(`推荐迭代次数: ${optimal}`);

目标:单次派生耗时 200-500ms。在桌面端通常是 600k-1M,在低端手机上可能只能跑到 200k-400k。

3.3 为什么不用 SHA-256 简单哈希?#

// ❌ 错误示范
const wrong = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('password123'));
// 1ms 就能算出 1 亿次!
// ✅ 正确:慢哈希 + 盐
const right = await deriveKey('password123', userSalt, 600_000);
// 600k 次 SHA-256 + 16 字节盐 + 不可导出

⚠️ Web Crypto 没有 Argon2。Argon2 是目前抗 GPU/ASIC 破解最强的算法,但浏览器不支持。Worklet 或 WASM(libsodium.js)可以补全,但增加 ~150KB 体积。

四、ECDH 密钥交换:端到端加密的基石#

要实现「服务端永远看不到明文」的端到端加密(E2EE),需要客户端和客户端之间协商出一个共享密钥。ECDH(椭圆曲线 Diffie-Hellman)就是干这个的。

4.1 ECDH 原理(30 秒版)#

  1. Alice 生成自己的椭圆曲线密钥对 (a, A),其中 A = a × G(G 是基点)。
  2. Bob 也生成 (b, B)
  3. Alice 把 A 发给 Bob,Bob 把 B 发给 Alice。
  4. Alice 计算 a × B = a × (b × G) = abG
  5. Bob 计算 b × A = b × (a × G) = abG
  6. 双方得到了相同的共享密钥 abG,而中间人只看到 AB,无法反推出 ab (离散对数难题)。

4.2 完整代码:双方协商共享密钥#

/**
* 生成 ECDH 密钥对(P-256 曲线)
*/
async function generateECDH() {
return crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true, // 可导出(公钥要发给别人)
['deriveKey', 'deriveBits']
);
}
/**
* 导出公钥为 raw bytes
*/
async function exportPublic(keyPair) {
const raw = await crypto.subtle.exportKey('raw', keyPair.publicKey);
return new Uint8Array(raw);
}
/**
* 用自己的私钥 + 对方公钥派生共享密钥
*/
async function deriveSharedKey(myPrivateKey, theirPublicRaw) {
const theirPub = await crypto.subtle.importKey(
'raw',
theirPublicRaw,
{ name: 'ECDH', namedCurve: 'P-256' },
false,
[]
);
return crypto.subtle.deriveKey(
{ name: 'ECDH', public: theirPub },
myPrivateKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
// === Alice 端 ===
const alice = await generateECDH();
const alicePub = await exportPublic(alice);
// 把 alicePub 发送给 Bob(明文传输,**公钥不是秘密**)
// ... 网络传输 ...
// === Bob 端 ===
const bob = await generateECDH();
const bobPub = await exportPublic(bob);
// 收到 alicePub
const aliceShared = await deriveSharedKey(bob.privateKey, alicePub);
// 把 bobPub 发回给 Alice
// ... 网络传输 ...
// === Alice 收到 bobPub ===
const bobShared = await deriveSharedKey(alice.privateKey, bobPub);
// 现在 aliceShared 和 bobShared 是同一个 AES 密钥
// 可以用它加密消息:
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
aliceShared,
new TextEncoder().encode('https://e2ee-secret-message')
);

4.3 中间人攻击(MITM)问题#

裸的 ECDH 有一个致命缺陷:公钥在传输过程中可以被替换。Mallory 拦截 Alice 的公钥,自己生成一对密钥,把自己的公钥发给 Bob,Bob 以为自己在和 Alice 通信。

解决方案:数字签名 + 信任锚。Alice 在发送公钥时,用长期身份密钥(RSA / Ed25519)签名。Bob 收到后,用已知的 Alice 公钥(从 CA、TOFU、或 QR 码)验证签名。

/**
* 用 Ed25519 签名 ECDH 公钥(解决 MITM)
*/
async function signECDHWithIdentity(ecdhPubRaw, identityPrivateKey) {
return crypto.subtle.sign('Ed25519', identityPrivateKey, ecdhPubRaw);
}
async function verifyECDHFromIdentity(ecdhPubRaw, sig, identityPublicKey) {
return crypto.subtle.verify('Ed25519', identityPublicKey, sig, ecdhPubRaw);
}

五、AES-GCM 实战:加密敏感消息#

拿到了对称密钥(无论是 ECDH 协商还是 PBKDF2 派生),加密流程都一样。

5.1 一个完整的端到端加密消息系统#

class SecureChannel {
#key; // CryptoKey,外部无法访问
constructor(key) {
this.#key = key;
}
/**
* 加密消息,返回「IV + 密文」的拼接
*/
async encrypt(plaintext) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const cipherBuf = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
this.#key,
new TextEncoder().encode(plaintext)
);
// 前 12 字节是 IV,后面是密文(包含 GCM 标签)
const out = new Uint8Array(iv.byteLength + cipherBuf.byteLength);
out.set(iv, 0);
out.set(new Uint8Array(cipherBuf), iv.byteLength);
return out;
}
/**
* 解密(自动验证 GCM 认证标签)
*/
async decrypt(packed) {
const iv = packed.slice(0, 12);
const cipher = packed.slice(12);
const plainBuf = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
this.#key,
cipher
);
return new TextDecoder().decode(plainBuf);
}
}
// === 使用 ===
const aesKey = await deriveKey('shared-passphrase', salt, 600_000);
const channel = new SecureChannel(aesKey);
const encrypted = await channel.encrypt('银行卡号: 6225 1234 5678 9012');
// 存入 IndexedDB / 发送到服务端(服务端看到的是乱码)
const decrypted = await channel.decrypt(encrypted);
console.log(decrypted); // '银行卡号: 6225 1234 5678 9012'

5.2 IV 重用:最容易犯的致命错误#

AES-GCM 的安全性有一个硬性约束同一密钥下,每个 IV 只能用一次。如果两次加密用了相同的 (key, iv) 组合,攻击者可以直接恢复出明文(包括明文 XOR、密钥流)。

// ❌ 致命错误:固定 IV
const fixedIV = new Uint8Array(12); // 全 0
async function brokenEncrypt(plain) {
return new Uint8Array(await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: fixedIV },
key,
new TextEncoder().encode(plain)
));
}
// ✅ 正确:每次随机 IV
async function safeEncrypt(plain) {
const iv = crypto.getRandomValues(new Uint8Array(12));
// ...
}
// ✅ 更好:用计数器(确定性加密场景)
// 维护一个全局自增的 12 字节计数器
let counter = 0n;
async function deterministicEncrypt(plain) {
const iv = new Uint8Array(12);
new DataView(iv.buffer).setBigUint64(4, counter++); // 后 8 字节自增
return crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, new TextEncoder().encode(plain));
}

检查清单:每次加密前,确认 iv 来自 crypto.getRandomValues(new Uint8Array(12)) 或单调递增的计数器。

六、实战:构建一个零知识笔记应用#

把上面所有东西串起来,做一个「服务端永远看不到明文」的笔记应用。

6.1 架构#

用户密码 ─PBKDF2→ AES 主密钥(不存储,每次输入密码派生)
AES-GCM 加密
密文(IV + 密文 + 标签)
服务端存储

服务端永远只有密文和用户盐。即便数据库泄露,没有用户密码的攻击者拿不到明文。

6.2 完整代码#

secure-notes.js
const PBKDF2_ITERATIONS = 600_000;
const PBKDF2_HASH = 'SHA-256';
const AES_LENGTH = 256;
/** 从用户密码派生 AES 密钥(每次登录/解锁时调用) */
async function unlockVault(password, saltB64) {
const salt = Uint8Array.from(atob(saltB64), c => c.charCodeAt(0));
const baseKey = await crypto.subtle.importKey(
'raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveKey']
);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: PBKDF2_ITERATIONS, hash: PBKDF2_HASH },
baseKey,
{ name: 'AES-GCM', length: AES_LENGTH },
false,
['encrypt', 'decrypt']
);
}
/** 加密单个笔记 */
async function encryptNote(key, title, content) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const payload = JSON.stringify({ title, content, ts: Date.now() });
const cipher = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
new TextEncoder().encode(payload)
);
return {
iv: btoa(String.fromCharCode(...iv)),
cipher: btoa(String.fromCharCode(...new Uint8Array(cipher))),
};
}
/** 解密单个笔记 */
async function decryptNote(key, { iv, cipher }) {
const ivBytes = Uint8Array.from(atob(iv), c => c.charCodeAt(0));
const cipherBytes = Uint8Array.from(atob(cipher), c => c.charCodeAt(0));
const plain = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: ivBytes },
key,
cipherBytes
);
return JSON.parse(new TextDecoder().decode(plain));
}
// === 用户注册:服务端生成 salt 并保存 ===
async function registerUser(username, password) {
const salt = crypto.getRandomValues(new Uint8Array(16));
// 服务端只需要存:username, salt,**永远不存密码哈希**
await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
salt: btoa(String.fromCharCode(...salt)),
}),
});
}
// === 用户登录 ===
async function loginAndCreateNote(username, password, noteText) {
// 1. 拉取 salt
const res = await fetch(`/api/salt?username=${username}`);
const { salt } = await res.json();
// 2. 派生密钥
const key = await unlockVault(password, salt);
// 3. 加密笔记
const encrypted = await encryptNote(key, '我的秘密', noteText);
// 4. 发送密文到服务端
await fetch('/api/notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, ...encrypted }),
});
// 5. 密钥变量出作用域,等 GC 回收
return true;
}

6.3 服务端只看到什么?#

{
"username": "alice",
"iv": "8xK2pQ7nT5...",
"cipher": "9zL4mR1sV8..."
}

服务端完全无法恢复出 noteText。数据库泄露、员工越权、服务器被攻陷——攻击者拿到的只有密文。

七、关键安全清单#

写完代码,按这个清单逐项检查:

检查项标准怎么验证
HTTPS部署到 HTTPS 或 localhostwindow.isSecureContext
IV 唯一同一密钥下 IV 不重复每次 crypto.getRandomValues(new Uint8Array(12))
盐随机每个用户/每次派生独立16 字节 CSPRNG
PBKDF2 迭代≥ 600,000实际测量耗时 ≥ 200ms
密钥不导出extractable: falsederiveKey 时设置
密钥清理内存中不残留明文闭包退出 + 主动 unset
算法现代不用 MD5 / SHA1 / AES-CBC改用 SHA-256 / AES-GCM
JWT 算法不用 alg: none服务端严格校验 alg
时序攻击HMAC 比较用 crypto.subtle.verify不要 === 手动比
跨标签页同步BroadcastChannel + 派生密钥不要直接传密钥

八、性能数据#

在我手头的 M2 MacBook 上做的一组基准测试(仅供参考):

操作耗时备注
生成 ECDH P-256 密钥对2.1ms单次
ECDH 派生 AES 密钥1.4ms双方各一次
AES-GCM 加密 1KB0.05ms极快
AES-GCM 加密 1MB8ms实测
HMAC-SHA256 签名0.02ms32 字节输出
PBKDF2 派生(600k 迭代)280ms桌面端典型值
PBKDF2 派生(600k 迭代)1.2siPhone 13

关键启示:在桌面端 PBKDF2 600k 迭代约 280ms,用户可接受。移动端可能要降到 200k-400k。

九、常见错误(线上事故总结)#

9.1 alg: none 漏洞#

很多年前 JWT 库允许 alg: none,即无签名。攻击者直接把 header 改成 {"alg":"none"} 就能伪造 Token。

// ❌ 攻击载荷
const malicious = btoa(JSON.stringify({ alg: 'none', typ: 'JWT' }))
+ '.'
+ btoa(JSON.stringify({ userId: 999, role: 'admin' }))
+ '.';
// 没有任何 signature 段
// ✅ 防御:服务端只接受白名单算法
// const allowedAlgs = ['HS256', 'RS256'];
// verify(token, key, { algorithms: allowedAlgs });

9.2 localStorage 存 Token + XSS#

Token 放 localStorage,被 XSS 一扫就完蛋。

// ❌ 错误
localStorage.setItem('token', jwt);
// ✅ 正确:存内存 + httpOnly Cookie 双管齐下
// 1. 内存(用于当前请求)
let inMemoryToken = null;
function setToken(t) { inMemoryToken = t; }
function getAuthHeader() { return inMemoryToken ? `Bearer ${inMemoryToken}` : ''; }
// 2. 刷新 Token 用 httpOnly Cookie(防 XSS)
// 服务端:res.cookie('refresh', token, { httpOnly: true, secure: true, sameSite: 'strict' });

9.3 HMAC 时序攻击#

a === b 这种字符串比较,会因为「第一个不同字符的位置」泄露前缀信息。攻击者可以逐字节猜出 HMAC 值。

// ❌ 时序攻击
function badVerify(received, expected) {
return received === expected;
}
// ✅ 常时比较(用 Web Crypto 自带 verify)
const ok = await crypto.subtle.verify('HMAC', key, sig, msg);
// 内部用恒定时间算法

十、延伸阅读#

  1. MDN - Web Crypto APIhttps://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API
  2. OWASP Password Storage Cheat Sheethttps://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
  3. JWT Best Current Practices (RFC 8725)https://datatracker.ietf.org/doc/html/rfc8725
  4. NIST SP 800-38D (AES-GCM)https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
  5. Signal Protocol(端到端加密参考实现)https://signal.org/docs/

总结:Web Crypto API 提供了和系统级加密库同级别的能力,但接口设计偏底层。生产中要关注的不是「能不能实现」,而是「参数对不对、IV 用没用对、密钥有没有泄露」。把这份清单和文章里的代码模板存下来,几乎能覆盖 90% 的前端认证场景。

剩下的 10%?那就要上 Signal 协议了,下次再聊。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Web Crypto 安全认证实战:从 JWT 签名到端到端加密
https://boke.hackerdream.xyz/posts/web-crypto-secure-auth/
作者
晴天
发布于
2026-06-05
许可协议
CC BY-NC-SA 4.0
相关文章 智能推荐
1
前端 CORS 跨域与 Cookie 策略:SameSite、跨域携带凭证实战
浏览器与安全 深入解析 CORS 跨域资源共享机制与 Cookie 安全策略,从 SameSite 属性到 credentials 跨域携带,涵盖预检请求、凭证模式、常见死锁与调试指南,附完整前后端代码示例。
2
AI 函数调用与 MCP 协议深度解析:从 OpenAI tools 到 Model Context Protocol
AI 系统拆解 AI 函数调用(Function Calling/Tool Use)与 Model Context Protocol(MCP)的来龙去脉。从 OpenAI tools 协议栈、Claude Tool Use、Gemini Function Calling 的差异,到 MCP 协议的 JSON-RPC 通信、Resources/Prompts/Tools/Sampling 四大原语与 stdio/SSE/HTTP 三种传输,再到自建 MCP Server 接入 Claude Desktop 与 Claude Agent SDK 的全链路实战,附 6 个完整可运行示例。
3
LLM 结构化输出实战:JSON Schema、函数调用与 Zod 校验全链路
AI 深入剖析 LLM 结构化输出的三种主流方案——JSON Mode、Function Calling/Tool Use 与 JSON Schema 约束式生成。从原理、可靠性、Token 成本到前端解析与 Zod 运行时校验,搭配 8 个完整可运行示例,帮你把 AI 的'自由发挥'装进确定的格子里。
4
Web Worker 与 SharedArrayBuffer:主线程阻塞的终极解法
浏览器与性能 当主线程遇到 10 万行数据、密码学运算、实时音视频解码时,Dedicated Worker 已经救不了你——你需要 SharedArrayBuffer + Atomics + Comlink 三件套。本文从痛点出发,拆解跨 Worker 共享内存的底层机制、COOP/COEP 跨域隔离配置,并给出可立即复用的图像处理实战项目。
5
Web Vitals 性能监控全链路实战:从 LCP/CLS/INP 到真实用户度量
浏览器与性能 前端性能优化不能靠感觉,Web Vitals 提供了可量化的真实用户体验指标。本文将系统拆解 LCP、INP、CLS 三大核心指标的底层原理、采集方案、归因分析与优化实战,并手写一套生产级 RUM 监控 SDK。
随机文章 随机推荐
Profile Image of the Author
晴天
Hello, I'm 晴天.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
155
分类
24
标签
387
总字数
345,424
运行时长
0
最后活动
0 天前

目录