前言

JWT(全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

简单业务流程

通过NodeJS生成JWT(保姆级教程,原理分析)

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 校验采用的方式