前言
JWT(全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
简单业务流程
JWT组成
Jwt由 Header.Payload.Signature 三部分数据拼接组成
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3d3dy5leGFtcGxlcy5jb20iLCJpYXQiOjE2NTM0ODI5NjAsImV4cCI6MTY1NDA4Nzc2MCwibmJmIjoxNjUzNDgyOTYwLCJqdGkiOiIyMjdjYTFmZi03ZGE1LTQzMGUtOTFjMy05ZTQxMDdjNWQ0YTgiLCJzdWIiOjF9.6LXlvsswMsKJpHD3ChsSGOzEJnYwm6eF212JE7Uc4p4
Header
示例
描述JWT元数据的Json对象,一般情况如下
{
typ: "JWT",
alg: "HS256",
}
解码header
// header数据为上面示例的第一部分
const header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
console.log(Buffer.from(header, "base64").toString("utf8"));
// 输出:{"alg":"HS256","typ":"JWT"}
Payload
示例
描述JWT元数据的Json对象,一般情况如下
{
// 发行方
iss: "https://www.examples.com",
// 签发日期
iat: 1653482960,
// 到期日期
exp: 1654087760,
// 不可用日期(在此之前)
nbf: 1653482960,
// JWT ID用于标识该JWT
jti: "227ca1ff-7da5-430e-91c3-9e4107c5d4a8",
// subject的缩写不知道该如何翻译,不过一般存储的都是 用户ID
sub: 1
}
解码Payload
因为payload仅为base64加密,很容易解码。所以不要在Payload中存储敏感信息。
// payload数据为上面示例的第一部分
const payload="eyJpc3MiOiJodHRwczovL3d3dy5leGFtcGxlcy5jb20iLCJpYXQiOjE2NTM0ODI5NjAsImV4cCI6MTY1NDA4Nzc2MCwibmJmIjoxNjUzNDgyOTYwLCJqdGkiOiIyMjdjYTFmZi03ZGE1LTQzMGUtOTFjMy05ZTQxMDdjNWQ0YTgiLCJzdWIiOjF9";
console.log(Buffer.from(payload, "base64").toString("utf8"));
// 输出:{"iss":"https://www.examples.com","iat":1653482960,"exp":1654087760,"nbf":1653482960,"jti":"227ca1ff-7da5-430e-91c3-9e4107c5d4a8","sub":1}
Signature
因为本文模拟数据中 Header 中的 alg 为 Hs256 所以,加密使用 HMAC-SHA256 作为签名算法
// 秘钥 此秘钥不要泄漏。一般存放在后端的配置文件汇总
const secret =
"lbOAmnKu9xpvjEz7TDUFLf2RXMCw1NeGBo04ksgWQidJy3SVPcH85atZhYrI6qxx";
// header头,与前文设置一样,进行base64加密
const header = Buffer.from(
JSON.stringify({
alg: "HS256",
typ: "JWT",
}),
"base64"
).toString("utf8");
// 载荷,与前文设置一样,进行base64加密
const payload = Buffer.from(
JSON.stringify({
iss: "https://www.examples.com",
iat: 1653482960,
exp: 1654087760,
nbf: 1653482960,
jti: "227ca1ff-7da5-430e-91c3-9e4107c5d4a8",
sub: 1,
}),
"base64"
).toString("utf8");Ï
// 构造 hmac
const hmac = crypto.createHmac("sha256", secret);
// 设置加密文本, 采用 header.payload 的连接方式
hmac.update(header + "." + payload);
// 进行签名后,通过 hex 获取 base64 摘要。
let sig = hmac.digest("base64");
// base的结果中可能尾随一个或多个= 补位,需要去除
sig = sig.replace(/=+$/ig,'');
console.log(sig);
// 输出: 6LXlvsswMsKJpHD3ChsSGOzEJnYwm6eF212JE7Uc4p4
与前文中的token第三段 6LXlvsswMsKJpHD3ChsSGOzEJnYwm6eF212JE7Uc4p4 一致。当 header+payload一致时且秘钥相同,则生成的签名一致。这个也是JWT 校验采用的方式