为了解决 Session + Cookie 机制暴露出的诸多问题,我们可以使用 Token 的登录方式。

  1. 1. Token 是服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登录后,服务器会生成一个 Token 并返回给客户端,客户端后续访问时,只需带上这个 Token 即可完成身份认证。

🥇 Token 机制实现流程

用户首次登录时:

  1. 1. 用户输入账号密码,并点击登录。
  2. 2. 服务器端验证账号密码无误,创建 Token
  3. 3. 服务器端将 Token 返回给客户端,由***客户端自由保存***

image.png
后续页面访问时:

  1. 1. 用户访问 a.com/pageB 时,带上第一次登录时获取的 Token
  2. 2. 服务器端验证 Token ,有效则身份验证成功。

image.png

🥇 Token 机制的特点

根据上面的案例,我们可以分析出 Token 的优缺点:

  1. 1. 服务器端不需要存放 Token,所以不会对服务器端造成压力,即使是服务器集群,也不需要增加维护成本。
  2. 2. Token 可以存放在前端任何地方,可以不用保存在 Cookie 中,提升了页面的安全性。
  3. 3. Token 下发之后,只要在生效时间之内,就一直有效,如果服务器端想收回此 Token 的权限,并不容易。

🥇 Token 的生成方式

最常见的 Token 生成方式是使用 JWT(Json Web Token),它是一种简洁的,自包含的方法用于通信双方之间以 JSON 对象的形式安全的传递信息。
上文中我们说到,使用 Token 后,服务器端并不会存储 Token,那怎么判断客户端发过来的 Token 是合法有效的呢?
答案其实就在 Token 字符串中,其实 Token 并不是一串杂乱无章的字符串,而是通过多种算法拼接组合而成的字符串,我们来具体分析一下。
JWT 算法主要分为 3 个部分:header(头信息),playload(消息体),signature(签名)。
header 部分指定了该 JWT 使用的签名算法:

  1. header = '{"alg":"HS256","typ":"JWT"}' // `HS256` 表示使用了 HMAC-SHA256 来生成签名。

playload 部分表明了 JWT 的意图:

  1. payload = '{"loggedInAs":"admin","iat":1422779638}' //iat 表示令牌生成的时间

signature 部分为 JWT 的签名,主要为了让 JWT 不能被随意篡改,签名的方法分为两个步骤:

  1. 1. 输入 base64url 编码的 header 部分、 . base64url 编码的 playload 部分,输出 unsignedToken
  2. 2. 输入服务器端私钥、unsignedToken,输出 signature 签名。
  1. const base64Header = encodeBase64(header)
  2. const base64Payload = encodeBase64(payload)
  3. const unsignedToken = `${base64Header}.${base64Payload}`
  4. const key = '服务器私钥'
  5. signature = HMAC(key, unsignedToken)
  1. 最后的 Token 计算如下:
  1. const base64Header = encodeBase64(header)
  2. const base64Payload = encodeBase64(payload)
  3. const base64Signature = encodeBase64(signature)
  4. token = `${base64Header}.${base64Payload}.${base64Signature}`

服务器在判断 Token 时:

  1. const [base64Header, base64Payload, base64Signature] = token.split('.')
  2. const signature1 = decodeBase64(base64Signature)
  3. const unsignedToken = `${base64Header}.${base64Payload}`
  4. const signature2 = HMAC('服务器私钥', unsignedToken)
  5. if(signature1 === signature2) {
  6. return '签名验证成功,token 没有被篡改'
  7. }
  8. const payload = decodeBase64(base64Payload)
  9. if(new Date() - payload.iat < 'token 有效期'){
  10. return 'token 有效'
  11. }