前后端人员一次就能读懂的jwt

124 阅读5分钟

JWT介绍

JWT是(JSON Web Token)缩写,是一个开放标准,是目前最流行的跨域解决方案,用于将各方数据信息作为JSON格式进行对象传递,可以对数据进行可选的数字加密,可使用RSA或ECDSA进行公钥/私钥签名。

最简单的举例理解就是自己生成一个JWT

const base64url = require('base64url');
const CryptoJS = require('crypto-js');

const header = {
  "alg": "HS256",
  "typ": "JWT"
}
const payload = {
  "email": 'dftxg@wepie.com',
  "exp": 1675737147,
  "iat": 1675650747,
  "name": '东风t西瓜',
  "userid": '15522717589805983'
}

const HeaderEncodeStr = base64url(JSON.stringify(header)) // eyJUeXAiOiJKV1QiLCJBbGciOiJIUzI1NiIsIkN0eSI6IiJ9
const PayloadEncodeStr = base64url(JSON.stringify(payload)) //  eyJsaZk-aihSIsInVzZXJpZCI6IjE1NTIyNzE3NTg5ODA1OTgzIn0

// 这个密钥只有服务器才知道,不能泄露给用户;一般可以用文件fs.readFile('')
const secret = 'Awc0YjY4ZjUyMmJkMzJlNzNQMmU0NzF2' 
const Signature = CryptoJS.HMACSHA256(
    `${HeaderEncodeStr}.${PayloadEncodeStr}`,
     secret) 
console.log(Signature) // 5UJkhXo_JQDiFrDoVDdFGoinBNZyKBS78jYmBk6ZBUc
const jwt = HeaderEncodeStr + "." + PayloadEncodeStr  + "." + Signature

也就是headerpayload经过base64Url后得到的字符串,用.拼接,然后再用HMACSHA256以及给定的密钥进行加密得到Signature将三者用.拼接,就可以得到传说中的含有用户等信息的jwt了;

为了显示方便,我会将payloadEncodeStr进行缩减,也就是篡改,其实这个token就是失效的了; 写成一行就是这个样子

Header.Payload.Signature

1.png

1.jwt的三个部分依次是

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

1.1 Header头部

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

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

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

最后,将上面的 JSON 对象使用 Base64URL

1.2 Payload负载

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,你还可以在这个部分定义私有字段,最开头的就是一个例子。

  "email": 'dftxg@wepie.com',
  "exp": 1675737147,
  "iat": 1675650747,
  "name": '东风t西瓜',
  "userid": '15522717589805983'

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

1.3 Signature

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
  base64url(JSON.stringify(header)) + "." +
  base64url(JSON.stringify(payload)),
  secret)

注意base64Url和base64有区别,JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+//,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-/替换成_。这就是 Base64URL 算法。

2. JWT使用方式

客户端收到服务器返回的JWT, 可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization或者其他和服务器约定的字段中。

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面

3.相比传统session的区别

传统的用户认证流程

  • 1、用户向服务器发送用户名和密码。
  • 2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
  • 3、服务器向用户返回一个 session_id,写入用户的 Cookie。
  • 4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
  • 5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

比起传统的session认证方案,为了让服务器能识别是哪一个用户发过来的请求,都需要在服务器上保存一份用户的登录信息(通常保存在内存中),再与浏览器的cookie打交道。

安全方面 由于是使用cookie来识别用户信息的,如果cookie被拦截,用户会很容易受到跨站请求伪造的攻击。

负载均衡 当服务器A保存了用户A的数据之后,在下一次用户A服务器A时由于服务器A访问量较大,被转发到服务器B,此时服务器B没有用户A的数据,会导致session失效。

内存开销 随着时间推移,用户的增长,服务器需要保存的用户登录信息也就越来越多的,会导致服务器开销越来越大。

4. JWT优势和弊端

优势

  • 数据体积小,传输速度快
  • 无需额外资源开销来存放数据
  • 支持跨域验证使用 弊端
  • 生成出来的Token无法撤销,即使重置账号密码之前的Token也是可以使用的(需等待JWT过期)
  • 无法确认用户已经签发了多少个JWT
  • 不支持refreshToken(参考无感刷新token)

有生成就能反解,

服务器拿到客户端发送来的JWT,就可以反向解析出里面的信息,可以查看是否过期,通过自己的密钥校验jwt是否被篡改等;

实践

查看JWT在钉钉SSO登录中的实践;以及在node服务端的实践