JWT(Json Web Token)

208 阅读4分钟

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了jwt字符串,第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature)。

  • JWT 长这个样
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpcCI6IjEyNy4wLjAuMSIsInV1aWQiOiJmZjEyMTJmNS1kOGQxLTQ0OTYtYmY0MS1kMmRkYTczZGUxOWEiLCJpYXQiOjE1Mjc1MjMwMTd9.
1C01cpOf1N3M9YAYvQjfcLbDGHZ4iPVncCGIoG-lpO0jHOIA_ZHtSMDvK1nzArLpGK5syQSwExsZJz2FJsd2W2TUiHQYtzmQTU8OBXX6mfSZRlkts675W5_WhIiOEwz69GFSD0AKXZifCRgIpKLC0n273MRMr0wJnuBi9ScfJ7YjSiqCr7qyQ5iXeOdS3ObT3wdjjk-Wu9wbWM7R25TFb-7PEZY7r_e8jmczPCVcNbOYegedu73T4d30kRn2jKufTGntD5hR6T9AQsgAMwVR1edEFflWb772TmrHI7WZOAivsBCN9sr4YiyYMvE8lcz_mBsgsunugGiHA3DGxB2ORbjIC8NPm8FI25zGOh9JIM2r_jFFTIm9GiuKtC8Ck8N3-eWi9u1NgBxwLdgN5JyCORnIOlciQEsScg-3SdCTM5LH_j6CeqQNwJxT4-oENzqLSTDJbP-SOj9nnx8HnJ5wh3n64rAvtc89CeTk7PhWFjksHDifngN-cnaszl5lqoF1enz5i9FYYELSjM-G7jns2SyY1MQeLRjuEDriPZtFaGmTW-RLH3gJfQXtbdpEo0_nHBqXEohwoN_FLKo4BNrEwshpyA7vkBpCQC0QALKyC1_L1Q5qduR6dDcqRozAo2VqJXmAKN0rvhLuIEHZkicOZY0Ds4So4GCcleqvFcxm1HQ

眼睛看仔细一些,你会发现 JWT 里面有两个.

数据格式是这样的 header.payload.signature

Header

JWT 的 header 中承载了两部分信息

{
  "alg": "RS256",
  "typ": "JWT"
}
  • alg: 声明加密的算法
  • typ: 声明类型

对这个头部信息进行 base64,即可得到 header 部分

const headerBuff = Buffer.from(
  JSON.stringify({
    alg: "RS256",
    typ: "JWT"
  })
);

const header = headerBuff.toString("base64");

console.log(header);
// eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9

Payload

payload 是主体部分,意为载体,承载着有效的 JWT 数据包,它包含三个部分

  • 标准声明
  • 公共声明
  • 私有声明

标准声明的字段

interface Stantar {
  iss?: string; // JWT的签发者
  sub?: string; // JWT所面向的用户
  aud?: string; // 接收JWT的一方
  exp?: number; // JWT的过期时间
  nbf?: number; // 在xxx日期之间,该JWT都是可用的
  iat?: number; // 该JWT签发的时间
  jti?: number; //JWT的唯一身份标识
}

标准中建议使用这些字段,但不强制。

公共声明的字段

interface Public {
  [key: string]: any;
}

公共声明字段可以添加任意信息,但是因为可以被解密出来,所以不要存放敏感信息。

私有声明的字段

interface Private {
  [key: string]: any;
}

私有声明是 JWT 提供者添加的字段,一样可以被解密,所以也不能存放敏感信息。

上面的 JWT 的 payload 结构是这样的

{
  "ip": "127.0.0.1",
  "uuid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
  "iat": 1527523017
}

同样是通过 base64 加密生成第二部分的 payload

const payloadBuffer = Buffer.from(
  JSON.stringify({
    ip: "127.0.0.1",
    uuid: "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
    iat: 1527523017
  })
);

const payload = payloadBuffer.toString("base64");

console.log(payload);
// eyJpcCI6IjEyNy4wLjAuMSIsInV1aWQiOiJmZjEyMTJmNS1kOGQxLTQ0OTYtYmY0MS1kMmRkYTczZGUxOWEiLCJpYXQiOjE1Mjc1MjMwMTd9

Signature

signature 是签证信息,该签证信息是通过headerpayload,加上secret,通过算法加密生成。

公式 signature = 加密算法(base64(header),base64(payload), 密钥);

上面的 header 中,我们已经定义了加密算法使用 RS256,也已经实现了生成headerpayload,下面我们来生成 signature

const crypto = require("crypto");
const sign = crypto.createSign("SHA256");
const secret = `私钥,太长我就不贴出来了`;

sign.write(header + "." + payload);
sign.end();

const signature = sign
  .sign(secret, "base64")
  // 在JWT库中,已经把这些字符过滤掉了
  .replace(/=/g, "")
  .replace(/+/g, "-")
  .replace(///g, "_");

console.log(signature);

它是如何做身份验证的?

首先,JWT 的 Token 相当是明文,是可以解密的,任何存在 payload 的东西,都没有秘密可言,所以隐私数据不能签发 token。

而服务端,拿到 token 后解密,即可知道用户信息,例如本例中的uuid

有了 uuid,那么你就知道这个用户是谁,是否有权限进行下一步的操作。

Token 的过期时间怎么确定?

payload 中有个标准字段 exp,明确表示了这个 token 的过期时间.

服务端可以拿这个时间与服务器时间作对比,过期则拒绝访问。

如何防止 Token 被串改?

此时 signature字段就是关键了,能被解密出明文的,只有headerpayload

假如黑客/中间人串改了payload,那么服务器可以通过signature去验证是否被篡改过。

在服务端在执行一次 signature = 加密算法(base64(header),base64(payload), 密钥);, 然后对比 signature 是否一致,如果一致则说明没有被篡改。

所以为什么说服务器的密钥不能被泄漏。

如果泄漏,将存在以下风险:

  • 客户端可以自行签发 token
  • 黑客/中间人可以肆意篡改 token

JWT的用法:

  • 用户登录,程序员向服务器发送post请求,请求jwt;服务器给我们响应一个jwt
  1. 程序员需要将jwt存入localStroge;
  2. 配置axios,让库里所有的header自动带上jwt,一般在authorization文件里;
  3. 请求Get/userID
  • 服务器会查找authorization文件里是否有jwt
  1. 如果有立即将jwt解密;
  2. 将解密后的signatureuserIDpayloaduserID对比是否一致;
  3. 之后服务器去数据库里查找和userID对应的信息,转换成JSON数据,发送给前端.

参考