jsonwebtoken 教程
jsonwebtoken 提供 JWT 的签发、验证和解码能力。本章重点学习 sign()、verify()、decode()、过期时间和算法限制。
1. 安装与导入
npm install jsonwebtoken
npm install --save-dev @types/jsonwebtoken
当前项目启用了 esModuleInterop,可以写:
import jwt from "jsonwebtoken";
TypeScript 会根据当前 module: "commonjs" 配置转换模块,仍可使用:
ts-node server.ts
2. 准备 Secret
HS256 使用共享 Secret。不要把 Secret 写进 Git 仓库:
JWT_ACCESS_SECRET=请替换为足够长的随机值
统一读取并尽早检查:
function readRequiredEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`缺少环境变量:${name}`);
}
return value;
}
const accessTokenSecret = readRequiredEnv("JWT_ACCESS_SECRET");
生产环境应由密钥管理服务或部署平台注入 Secret。Secret 应具有足够随机性,不能使用 123456、项目名等可猜值。
3. jwt.sign():生成 Token
基础签名:
const token = jwt.sign(
{ role: "user" },
accessTokenSecret,
{
algorithm: "HS256",
subject: "user-123",
issuer: "nloop-api",
audience: "nloop-client",
expiresIn: "15m",
}
);
三个参数分别是:
payload:要携带的声明。secretOrPrivateKey:HMAC Secret 或非对称算法私钥。options:算法、有效期、主体、签发方等设置。
默认会添加 iat。官方文档说明 expiresIn、issuer、audience 等没有默认值,应主动配置需要的约束。
不要同时在 Payload 和 options 中重复设置同一声明。例如不要同时使用:
// 错误思路:exp 与 expiresIn 重复
jwt.sign(
{ exp: 1234567890 },
accessTokenSecret,
{ expiresIn: "15m" }
);
4. expiresIn:设置有效期
支持数字或时间字符串:
jwt.sign(payload, secret, { expiresIn: 900 }); // 900 秒
jwt.sign(payload, secret, { expiresIn: "15m" }); // 15 分钟
jwt.sign(payload, secret, { expiresIn: "7d" }); // 7 天
一个容易踩坑的细节:
expiresIn: 120 // 数字:120 秒
expiresIn: "120" // 无单位字符串:由 ms 语法按 120 毫秒解析
因此字符串一定写单位,例如 "120s"、"15m"、"7d"。
5. algorithm:签发时选择算法
algorithm 是单数,因为签发一个 Token 时只选择一个算法:
const token = jwt.sign(payload, accessTokenSecret, {
algorithm: "HS256",
expiresIn: "15m",
});
若不指定,jsonwebtoken 默认使用 HS256。教程仍显式声明它,让配置和安全边界更加清楚。
使用 RS256 时应传入 RSA 私钥,并使用至少 2048 位的 RSA 密钥。不要启用官方文档中仅为兼容旧系统提供的 allowInsecureKeySizes 或 allowInvalidAsymmetricKeyTypes。
6. jwt.verify():验证 Token
const decoded = jwt.verify(token, accessTokenSecret, {
algorithms: ["HS256"],
issuer: "nloop-api",
audience: "nloop-client",
});
同步调用成功时返回 Payload,失败时抛出异常。它会根据配置验证:
- Token 格式。
- 签名。
exp是否过期。nbf是否已经生效。- 算法是否在白名单中。
issuer、audience等是否匹配。
为什么要设置 algorithms
algorithms 是复数,因为验证方可能允许一个算法列表:
algorithms: ["HS256"]
即使库能根据密钥类型推导默认算法,也建议显式设置白名单,避免接受业务系统未计划使用的算法。
验证后的类型收窄
verify() 的结果仍是外部输入。不要直接通过类型断言宣称它一定符合业务类型:
interface AccessTokenPayload {
sub: string;
tokenType: "access";
role: "user" | "admin";
}
function isAccessTokenPayload(
value: string | jwt.JwtPayload
): value is jwt.JwtPayload & AccessTokenPayload {
return (
typeof value !== "string" &&
typeof value.sub === "string" &&
value.tokenType === "access" &&
(value.role === "user" || value.role === "admin")
);
}
const decoded = jwt.verify(token, accessTokenSecret, {
algorithms: ["HS256"],
issuer: "nloop-api",
audience: "nloop-client",
});
if (!isAccessTokenPayload(decoded)) {
throw new Error("Token Payload 格式不正确");
}
console.log(decoded.sub);
7. 处理验证错误
try {
const decoded = jwt.verify(token, accessTokenSecret, {
algorithms: ["HS256"],
});
console.log(decoded);
} catch (error: unknown) {
if (error instanceof jwt.TokenExpiredError) {
console.log("Token 已过期", error.expiredAt);
} else if (error instanceof jwt.NotBeforeError) {
console.log("Token 尚未生效", error.date);
} else if (error instanceof jwt.JsonWebTokenError) {
console.log("Token 格式或签名不正确");
} else {
throw error;
}
}
对外通常返回统一的 401 Unauthorized,不必向客户端暴露具体签名失败原因;详细原因可在不包含完整 Token 的内部日志中记录。
8. jwt.decode():只读取,不验证
const decoded = jwt.decode(token);
decode() 不验证签名,不应判断登录状态或权限。适合的用途包括:
- 调试时观察 Payload。
- 在真正验证前读取 Header 中的
kid,再选择验证公钥。 - 客户端展示非安全性信息,但服务端仍必须独立验证。
查看完整结构:
const decoded = jwt.decode(token, { complete: true });
if (decoded) {
console.log(decoded.header);
console.log(decoded.payload);
console.log(decoded.signature);
}
错误示例:
// 严禁:攻击者可以伪造 role
const decoded = jwt.decode(token) as { role: string };
if (decoded.role === "admin") {
// 执行管理员操作
}
9. 同步与异步形式
传入 callback 时使用异步形式:
jwt.sign(payload, secret, options, (error, token) => {
// ...
});
不传 callback 时是同步形式并直接返回或抛错。JWT 签名验证通常是纯计算;初学案例选择同步形式,流程更容易阅读。使用远程 JWKS 获取公钥时,verify() 的异步形式更有意义。
10. 本章结论
sign 生成带签名的 Token
verify 验证签名和声明,认证时必须使用
decode 只读取内容,不能建立信任
algorithm 签发时选择一个算法
algorithms 验证时限制允许的算法列表
expiresIn 设置有效期,字符串要明确单位