什么是 JWT ( JSON Web Token )

280 阅读4分钟

【摘要】介绍什么是 JWT,它的结构、如何创建、如何使用。

JWT ( JSON Web Token ) 是一种数据格式,它解决的是客户端传给服务器的数据不可信的问题。

假设客户端登录后,服务器返回一个凭证 token,它是一个 JSON 字符串:

{
  "name": "foo",
  "expire": "2024-1-1",
  "permission": ["read", "write"]  
}

客户端可以用这个 token 请求资源,如果 token 过期了,客户端只需修改其中的 expire 字段即可,服务器无法判断凭证是否曾被修改或伪造。

如何保证 token 不可被修改呢?答案是利用签名。

画板

这个过程可以分为 5 步:

  1. 用私钥 key 对 token 执行摘要算法,得到签名。
  2. 将 token 和签名一起返回给客户端。
  3. 客户端传递 token 和签名给服务器。
  4. 服务器对传过来的 token 和 key 再计算一次摘要。
  5. 对比两个签名,签名相同表示则 token 没有被篡改。

这里关键的地方在于,key 是服务器私有的,客户端无法得到,进而无法伪造签名,从而保证 token 数据是可信的。

以上过程和 JWT 有什么关系?

JWT 就是上述过程中传递的 (token, signature)的标准化结构。

JWT 的数据结构

JWT 是一个字符串,包含 3 部分,用 . 号分开。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJleHAiOiIyMDI0LTA5LTEzVDE0OjI0OjEzLjE0N1oiLCJuYW1lIjoiZm9vIiwiYWRtaW4iOnRydWV9.
ZjI0M2RiNmM1YTkxMDRjZjU4ODQxNzNjMTVkOGM0Mzk2ODVkMDc5N2M1NTQ3MmU4YzgyYTk5MjM4Yzc0M2M4Ng==

.号将 JWT 分成 header、payload、signature 三部分,它们都是 base64 字符串,转码后方便阅读。

{"alg":"HS256","typ":"JWT"}.
{"exp":"2024-09-13T14:24:13.147Z","name":"foo","admin":true}.
"f243db6c5a9104cf5884173c15d8c439685d0797c55472e8c82a99238c743c86"

下面依次介绍这 3 部分。

header

header 部分是一个 JSON 字符串,用来描述 JWT 的元数据。

{"alg":"HS256","typ":"JWT"}

其中,alg 表示签名的算法,默认是 HMAC SHA256,简写成 HS256;typ 表示 token 类型为 JWT。

payload

payload 部分也是 JSON,用来存放实际传递的数据。

JWT 规定了 7 个官方字段,可供选用。

iss (issuer):签发人

exp (expiration time):过期时间

sub (subject):主题

aud (audience):受众

nbf (Not Before):生效时间

iat (Issued At):签发时间

jti (JWT ID):编号

除了官方字段,你还可以自定义字段。

{
  "exp":"2024-09-13T14:24:13.147Z",

  "name":"foo",
  "admin":true
}

上面代码中的 name 和 admin 都是自定义的。

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

signature

signature 是对 header 和 payload 的签名,防止数据篡改。

将这两部分转成 base64 字符串后进行签名即可。

signature = HMACSHA256(
  base64(header) + '.' + base64(payload),
  key
)

JWT 如何使用

JWT 通常会在 HTTP 协议的 header 中传递。

Authorization : Bearer [jwt_str]

其中,Bearer 是 HTTP 协议的授权类型

Authorization: <type> <authorization-parameters>

其他授权类型有:

  • Basic:用于 http-basic 认证;
  • Bearer:常见于 OAuth 和 JWT 授权
  • Digest:MD5 哈希的 http-basic 认证 ( 已弃用 )
  • AWS4-HMAC-SHA256:AWS 授权

最后,若把 JWT 放在 URL 中,Base64 有三个字符 +/=在 URL 里有特殊含义,所以要被替换。+ 替换成 -/ 替换成 _= 被省略、。

JWT 的代码实现

下面用 Node.js 简单实现 JWT 的创建和检验(忽略 base64URL)。

JWT 包含 3 部分,signature 是对其他两部分的签名,签名的 KEY 保存在服务器中。

function jwt(header, payload, key) {
  const header64 = toBase64(JSON.stringify(header))
  const payload64 = toBase64(JSON.stringify(payload))

  const sign64 = toBase64(
    sign(`${header64}.${payload64}`, 'sha256', key)
  )

  return `${header64}.${payload64}.${sign64}`
}

function toBase64(str) {
  return Buffer.from(str).toString('base64')
}

用 Node.js 的 crypto 模块执行 sha256 摘要算法,即签名。

const crypto = require('crypto')

function sign(info, algm = 'sha256', key) {
  const hmac = crypto.createHmac(algm, key)
  hmac.update(info)
  return hmac.digest('hex')
}

注意签名使用的 KEY 保存在服务器上,不能交给客户端。

const KEY = '12345'
const header = { alg: 'HS256', typ: 'JWT' }

const payload = {
  exp: new Date(), // 官方字段

  // 自定义字段
  name: 'foo',
  admin: true,
}

const _jwt = jwt(header, payload, KEY)

验证 JWT 只需对客户端提交的 header 和 payload 再进行一次签名,检查两次签名是否一致。

function isVerified(jwt) {
  const [header, payload, signature] = jwt.split('.')
  const _signature = sign(`${header}.${payload}`, 'sha256', KEY)

  return toBase64(_signature) === signature
}

isVerified(_jwt) // true

客户端对 JWT 进行任何改动,都会使得签名和数据对应不上。

const fakePayload = { name: 'hacker' }
const [header64, payload64, sign64] = _jwt.split('.')

// 伪造信息
const fakeJwt = `${header64}.${toBase64(
  JSON.stringify(fakePayload)
)}.${sign64}`

// 伪造信息,伪造签名
const fakeKey = 'xxxxx'
const fakeJwt2 = jwt(header, fakePayload, fakeKey)

isVerified(fakeJwt) // false
isVerified(fakeJwt2, KEY) // false

参考

JSON Web Token 入门教程 | 阮一峰的网络日志