jwt 介绍

目录

  • 1,jwt 的出现
    • 问题
  • 2,jwt 介绍
  • 3,jwt 令牌的组成
    • 3.1,header
    • 3.2,payload
    • 3.3,signature
  • 4,验证
  • 5,总结

身份验证相关内容:
浏览器 cookie 的原理(详)
session 原理

1,jwt 的出现

在浏览器 cookie 的原理(详)这篇文章中介绍了身份验证的步骤:

在这里插入图片描述

简单来说,如果身份验证通过 session 完成,那【出入证】就是一个 cookie 信息,内容是 sessionId。但还有其他的问题需要解决:

问题

随着前后端分离的发展,一个产品的终端可能不止是浏览器,还有桌面应用、智能家居等设备。

jwt 介绍_第1张图片
通过上图中可以看到:这些设备都会和同一个服务器通信,一般都是 http 协议。

通常不同的产品线会有自己的服务器,产品内部数据一般和自己的服务器交互。但中心服务器仍有存在的必要,因为产品之间总会有数据需要共享。

这个中心服务器至少承担着认证和授权的功能,比如登录:各种设备发送消息到中心服务器,中心服务器响应一个【出入证】(令牌信息)。

问题来了:其他的设备还能使用 cookie 传递令牌信息吗?

虽然 cookie 简单来说就是一个消息头,但浏览器有完善的管理机制:比如自动保存和自动发送,还有相应的安全机制等。但其他设备上的 cookie 机制就需要手动处理了。

jwt 的出现就是为了解决这个问题。

2,jwt 介绍

全称为 json web token,目的:为不同的终端设备提供统一的、安全的令牌格式。

jwt 介绍_第2张图片
令牌信息在传输时就是一个字符串而已,而 jwt 是令牌格式。这个字符串可以简单理解为:对一些特殊信息做了编码和加密,来达到身份验证的目的。

所以对这个字符串来说,

  • 在客户端的存储位置没有限制,放到 cookielocalStorage、pc 文件、手机文件中都可以。
  • 传输方式也没有限制。一般来说,会使用消息头来传输它:

比如登录成功后,服务器可以给客户端响应一个 jwt令牌:

POST /api/login HTTP/1.1 200 OK
...
set-cookie: token=jwt令牌
authorization: bearer jwt令牌
...

{..., token:jwt令牌}

它可以出现在响应的任何位置,或是同时出现在多个位置。
以上面的响应为例,就是为了充分利用浏览器的 cookie 机制,同时为了照顾其他设备,所以也出现在了响应头 authorization 和响应体中。

虽然没有明确的要求应该如何附带到请求中,但通常都会如下的格式(OAuth2附带 token 的一种规范格式):

GET /api/resources HTTP/1.1
...
authorization: bearer jwt令牌
...

整体交互流程:

jwt 介绍_第3张图片

3,jwt 令牌的组成

为了保证令牌的安全性,jwt 令牌由3个部分组成:

  1. header,头部,记录令牌的类型和签名算法。
  2. payload,负载,记录主体信息,比如用户信息。
  3. signature,签名,按头部的签名算法对整个令牌签名,作用:保证令牌不被伪造和篡改。

完整格式为 header.payload.signature。举例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0._UuiFQ-rF8DZdheGE79LA46nfACrn2IiFPckUay7lQI

3.1,header

格式为 json 对象:

{
  "alg":"HS256",
  "typ":"JWT"
}
  1. alg 签名算法,默认是 HMAC SHA256 写成 HS256(对称加密算法);也可以使用 RS256(非对称加密算法)。
  2. typ 令牌类型,固定为 JWT

接着将 json 对象使用 base64 url 编码。

base64 url 不是加密算法,而是一种编码格式,它是在 base64 编码的基础上对 =+/ 这3个字符做特殊处理(=被省略,+替换为 -/ 替换为 _),因为 jwt 可能也会在 url 中传输。
base64 是使用64个可打印字符来表示一个二进制数据。

nodejs 需要借助第三方库实现,比如 base64url:

const base64url = require("base64url");

const a = base64url.encode(
  JSON.stringify({
    alg: "HS256",
    typ: "JWT",
  })
);
console.log(a); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

const b = base64url.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9");
console.log(b);

3.2,payload

jwt 的主体信息,也是一个 json 对象,包含以下内容:

{
  "ss""发行者", // 可以是公司名字,也可以是服务名称
  "iat""发布时间",
  "exp""到期时间",
  "sub""主题", // 该 jwt 的作用
  "aud""受众", // 发放给哪个终端的,可以是终端类型,也可以是用户名称
  "nbf""在此之前不可用", // 一个时间点,在该时间点到达之前,这个令牌是不可用的
  "jti""JWT ID" // jwt的唯一编号,主要是为了防止重放攻击(在某些场景下,用户使用之前的令牌发送到服务器,被服务器正确的识别,从而导致不可预期的行为发生)
}

以上内容只是一个规范,都是可选的。设置了也需要之后验证 jwt 令牌时手动处理才能发挥作用。

而我们可以把需要的信息加进去,比如用户 id 等等。比如:

{
  "foo": "myname", // 自定义信息
  "iat": "1703776637" // 规范的信息
}

同样也需要使用 base64url 编码:

// base64url 编码
const base64url = require("base64url");
const a = base64url.encode(
  JSON.stringify({
    foo: "myname",
    iat: "1703776637",
  })
);
// eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0

注意,浏览器提供的 window.btoa 函数只是 base64 编码,并不是base64 url 编码!不会对 =+/ 这3个字符做特殊处理。

window.btoa(JSON.stringify({
  "foo":"myname",
  "iat":"1703776637"
}))
// 'eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0='

// 但都可被正常解码,下面2个结果相同
window.atob('eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0=')
window.atob('eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0')

header 和 payload 都算是明文传输的。所以不要将敏感信息放到 payload 中

3.3,signature

这部分保证了 jwt 不会被篡改或伪造。

生成步骤:将 header 和 payload 的编码结果,使用 header 中指定的加密算法进行加密。

// 上面 header 的编码结果 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
// 上面 payload 的编码结果 eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0
const crypto = require("crypto");

function HS256(header, playload) {
  const hmac = crypto.createHmac("sha256", "mykey"); // 创建加密对象,且指定秘钥为 mykey
  hmac.update(`${header}.${playload}`); // 将数据放入加密对象
  return hmac.digest("base64url"); // 编码为 base64url 
}

HS256("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", "eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0")
// _UuiFQ-rF8DZdheGE79LA46nfACrn2IiFPckUay7lQI

最终将这3部分拼接在一起,得到完整的 jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJteW5hbWUiLCJpYXQiOiIxNzAzNzc2NjM3In0._UuiFQ-rF8DZdheGE79LA46nfACrn2IiFPckUay7lQI

node-hmac.digest 参考

4,验证

因为签名使用的秘钥会保存在服务器,所以客户端无法伪造签名来篡改 jwt。

服务器拿到客户端回传的 jwt 之后,除了验证相同之外(比如payload信息被篡改),还需要验证是否过期,受众是否还满足要求等。

最终整体验证流程:

jwt 介绍_第4张图片

5,总结

  • jwt本质上是一种令牌格式。它和终端设备无关,同样和服务器无关,甚至与如何传输无关,它只是规范了令牌的格式而已。
  • jwt由三部分组成:header、payload、signature,主体信息在payload。
  • jwt难以被篡改和伪造。这是因为有第三部分的签名存在。所以在秘钥不泄露的前提下,一个验证通过的 jwt token 是值得被信任的。

以上。

你可能感兴趣的:(浏览器,interview,jwt,前端,网络,身份验证,node.js)