# JSON Web Token(JWT)身份认证,前后端实践应用

TIP

当我们首次登录某个网站时,需要填写用户名和密码,登录成功后才能访问用户中心及相关页面。

在一定时内,当我们再次访问这个网站时,不需要登录即可访问用户中心相关页面。这背后就涉及 Token 身份验证。

# 一、Token 认证

TIP

深入浅出什么是 Token 和 Token 身份认证的流程图

# 1、什么是 Token

TIP

token 是一种用于身份认证的技术,它是一个包含用户信息签名字符串,通常由服务器生成并返回给客户端。

以下为 token 字符示例

// Token 有3个部分组成,中间用.链接
const token =
  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    .cThIIoDvwdueQB468K5xDc5633seEFoqwxjF_xSJyQQ;

注:

客户端在每次请求时,都需要携带 token 来证明自己的身份。服务器通过验证 token 的有效性和完整性,来授权客户端访问受保护的资源。

# 2、Token 身份验证的流程图

image-20230913183740980

TIP

通过上面流程图得知,要实现通过 Token 完成身份验证,需要解决以下 2 个核心问题

  • 如何生成 Token
  • 如何验证 Token

JSON Web Token(JWT) 这个插件就可以帮助我们解决上面提到的 2 个核心问题。

本章我们将详细讲解 JWT 的核心概念及实战应用,内容如下:

  • JWT 核心概念
  • JWT 基本使用
  • 实战应用:Token 身份验证

# 一、JWT 核心概念

TIP

本小节主要讲解 JWT 的核心概念:

  • 什么是 JSON Web Token
  • JSON Web Token 应用场景
  • 什么是 JSON Web Token 结构
  • JWT 调试器

# 1、什么是 JSON Web Token ?

TIP

JSON Web Token(JWT)是一个开放标准(RFC 7519 (opens new window)),它定义了一种紧凑自包含方式,用于在各方之间作为 JSON 对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。

JSON Web Token 简介详细查阅官方:https://jwt.io/introduction (opens new window)

# 1.1、密钥

TIP

用来对信息加密和解密

image-20231013173842078

# 1.2、公钥 和 私钥

TIP

当两者之间要进行安全信息交换或通信时,就需要用到公钥和私钥。

  • 公钥用来加密机密信息,以防被恶意人士窃取或篡改。
  • 私钥能够解密被加密的信息,以便有效地接收和使用该信息。

以下我们借助生活中投信件和送信件的例子来帮助大家理解公钥和私钥

image-20231013173109572

不过在本课程中,我们只讲解如何通过密钥,来生成 Token,实现身份验证。

# 2、JSON Web Token 应用场景

TIP

以下是 JSON Web Token(令牌)有用的一些场景:

  • 授权

这是使用 JWT 的最常见方案。一旦用户登录,每个后续请求将包括 JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On(单点登录)是一种现在广泛使用 JWT 的功能,因为它的开销很小,并且能够在不同的域中轻松使用。

  • 信息交换

JSON Web 令牌是在各方之间安全传输信息的好方法。因为 JWT 可以签名, 例如,使用公钥/私钥可以确定发件人是他们所说的人。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。

# 3、什么是 JSON Web Token 结构

TIP

在其紧凑的形式中,JSON Web 令牌由三个部分组成,由点(.)分隔,它们是:

  • Header 报头
  • Payload 有效载荷
  • Signature 签名

因此,JWT 通常看起来如下所示。我们对 Token 验证,需要分解这三部分,然后对签名作验证。

xxxxx.yyyyy.zzzzz   # 报头.有效载荷.签名

JWT 调试器地址:https://jwt.io/#debugger-io (opens new window) 可以用解码、验证和生成 JWT

image-20230912115555430

# 3.1、Header 标头

TIP

标头通常由两部分组成:令牌的类型(JWT)和使用的签名算法(如 HMAC SHA256 或 RSA)。

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

然后,上面这个 JSON 被 Base64Url 编码以形成 JWT 的第一部分。

注意:base64Url 加密后的数据是任何人都可以解密,相当于透明

# 3.2、Payload 有效载荷

TIP

JWT 的第二部分是有效负载,PayLoad 有效载荷用来声明关于实体(通常是用户)和附加数据的声明。你可以理解为我们真正要传递的用户信息和数据就放在这个部分。

以下为有效负载示例:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

有效负载的数据格式为 JSON 对象,被 Base64Url 编码以形成 JWT 的第二部分。

注意

对于签名令牌,此信息虽然受到防止篡改的保护,但任何人都可以读取。除非加密,否则请勿将秘密信息放入 JWT 的有效负载或标头元素中。

有效载荷声明为三种类型:注册声明、公开声明、私人声明

  • 注册声明

这些是一组预定义的声明,不是强制性的,而是官方推荐的,以提供一组有用的、可互操作的声明。

以下官方提供了 7 个注册声明(可选的)

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

请注意,声明名称只有三个字符,因为 JWT 旨在紧凑。

  • 公开声明

这些可以由使用 JWT 的人随意定义。但为了避免冲突,它们应该在IANA JSON Web 令牌注册表 (opens new window)中定义,或者定义为包含防冲突命名空间的 URI。

  • 私人声明

这些是为在同意使用它们的各方之间共享信息而创建的自定义声明(JWT 的生产者和消费者可以同意使用声明名称是私人名称),既不是注册声明也不是公开声明

# 3.3、Signature 签名

TIP

Signature 部分是对前两部分(Header 与 Payload)的签名,防止数据篡改。

要创建签名部分,您必须获取编码的标头、编码的有效负载、密钥、标头中指定的算法,然后对其进行签名。

例如,如果要使用 HMAC SHA256 算法,则将通过以下方式创建签名:

// 定义密钥
const secret = "xxsawwssewfksdflsow";

HMACSHA256(
  // 标头base64URL编码
  base64UrlEncode(header) +
    "." +
    // 有效负载 base64URL编码
    base64UrlEncode(payload),
  // 密钥
  secret
);

注:

签名用于验证消息在传输过程中没有发生更改,并且在使用私钥签名的令牌的情况下,它还可以验证 JWT 的发送者是否是其所说的人。

# 4、JWT 调试器

TIP

如果您想使用 JWT 并将这些概念付诸实践,您可以使用jwt.io 调试器 (opens new window)来解码、验证和生成 JWT。

JWT 调试器地址:https://jwt.io/#debugger-io (opens new window)

image-20230912132922265

# 二、JWT 的基本使用

TIP

本小节将学习如何在项目中安装 JWT 及使用,具体使用也可以查阅 JWT 使用 (opens new window)

# 1、安装

执行以下命令安装 jsonwebtoken

npm install jsonwebtoken

在项目中导入 jsonwebtoken

const jwt = require("jsonwebtoken");

# 2、生成 Token

TIP

jwt 的 sign 方法来生成 token,可以同步形式返回 token,也可以异步形式返回。

语法

sign(payload,secretOrPrivateKey,[options,callback]
  • payload 有效载荷可以是对象文字、缓冲区或表示有效 JSON 的字符串。

有效载荷可以以 exp 来标注 token 的过期时间,单位默认为秒,如:Math.floor(Date.now() / 1000) + (60 \* 60),表示过期时间为 1 小时

const token = jwt.sign(
  {
    exp: Math.floor(Date.now() / 1000) + 60 * 60, // 过期时间为1小时
  },
  secretOrPrivateKey
);
  • secretOrPrivateKey 表示密钥,可以是字符串(UTF-8 编码)、缓冲区、对象或 KeyObject,其中包含 HMAC 算法或 PEM 格式 RSA 密钥和 ECDSA 的加密私钥

  • options:可选参数,是一个对象,对象的具体属性值详细查阅地址:https://www.npmjs.com/package/jsonwebtoken (opens new window)。其中有两个常用的字段,如下:

    • header 字段来声明标头
    • expiresIn 字段用来声明过期时间,过期时间可以以字符串表示,也可以以数字表示。 以字符串表示,带上时间单位,否则默认使用毫秒为单位。 如:expiresIn: '10'表示 10ms 如:expiresIn: '10s'表示 10s 以数字表示:expiresIn: 10 表示 10s
    const token = jwt.sign(payload, secretOrPrivateKey, {
      expiresIn: "10s", // 10s
      // expiresIn: 60  // 60s
    });
    
  • callback:可先参数,回调函数,token 生成后会自动调用该回调

# 2.1、同步返回 Token

TIP

同步形式返回 jsonwebtoken

const token=jwt.sign(payload,secretOrPrivateKey,[options])

代码示例

const jwt = require("jsonwebtoken");
// 密钥
const secretOrPrivateKey = "abx1wseroohxsefe";
// 有效载荷
const payload = {
  exp: Math.floor(Date.now() / 1000) + 60 * 60, // 过期时间为1小时
  id: 1,
  username: "admin",
  nickname: "测试账号",
  avatar: "/img/avatar.png",
};

const token = jwt.sign(payload, secretOrPrivateKey);

// 或 添加options来标注令牌类型(JWT和使用的签名算法)
/*
const token=jwt.sign(payload,secretOrPrivateKey,{
   header:{
        "alg":"HS384",
        "typ":"JWT"
   } 
})
*/
// 打印token
console.log("token:", token);

执行 node jwt.js,最终生成如下 token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjYwLCJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsIm5pY2tuYW1lIjoi5rWL6K-V6LSm5Y-3IiwiYXZhdGFyIjoiL2ltZy9tZV9wYWdlL2F2YXRhci5wbmciLCJpYXQiOjE2OTQ1MTU1NDN9.gYQVnAXTXZF18hMTji_fH6ANTm73TgdPJwvErd3b0Pg

你也可以用 jwt 调试器 (opens new window)来验证 token,看是否能验证通过

image-20230912184737385

# 2.2、异步返回 Token

TIP

如果提供了回调函数,则会以异步形式返回 Token

const token=jwt.sign(payload,secretOrPrivateKey,options,callback)

代码示例

token 验证成功,则 err 和 token 被作为参数传入回调函数

const jwt = require("jsonwebtoken");
// 密钥
const secretOrPrivateKey = "abx1wseroohxsefe";
// 有效载荷
const payload = {
  id: 1,
  username: "admin",
  nickname: "测试账号",
  avatar: "/img/avatar.png",
};
// 异步方式返回token
jwt.sign(payload, secretOrPrivateKey, { expiresIn: "60s" }, (err, token) => {
  // 如果生成成功,则打印token
  if (token) {
    console.log("token:", token);
  } else {
    console.log("err:", err);
  }
});

# 3、验证 Token

TIP

jwt 的verify方法来验证 token,如果提供提供了回调,则函数异步运行。如果未提供回调,则函数同步运行

jwt.verify(token,secretOrPublicKey,[options],callback)

注:

  • token 为 jsonwebtoken 字符串
  • secretOrPublicKey 密钥,是一个字符串(UTF-8 编码)、缓冲区或 KeyObject,其中包含 HMAC 算法的密钥或 PEM RSA 和 ECDSA 的加密公钥
  • options 为可选参数,参数值参考官方:https://www.npmjs.com/package/jsonwebtoken (opens new window)
  • callback 可选参数,值为回调函数,解码后会自动调该回调函数,将 err,decoded 作为参数传入。

如果 token 验证失败,则会抛出错误,错误信息保存在 err 中; 如果 token 验证成功,则返回解码的有效载荷 ,保证在 decoded 中

# 3.1、验证 Token(同步)

TIP

  • 如果 verify 方法没有提供回调,则函数同步运行。
  • 如果签名验证成功,即签名有效并且可选的过期、访问者或颁发者有效,则返回解码的有效负载
  • 如果签名验证失败,将抛出错误
jwt.verify(token,secretOrPublicKey,[options]

代码示例

const jwt = require("jsonwebtoken");
// 密钥
const secretOrPrivateKey = "abx1wseroohxsefe";
// 有效载荷
const payload = {
  id: 1,
  username: "admin",
  nickname: "测试账号",
  avatar: "/img/avatar.png",
};
// 生成token
const token = jwt.sign(payload, secretOrPrivateKey, {
  expiresIn: "2s",
});

// token验证,因为有可能验证出错,会抛出错误,所以我们需要对错误捕获
try {
  const decoded = jwt.verify(token, secretOrPrivateKey);
  console.log(decoded);
} catch (err) {
  console.log("err");
}

执行node jwt.js命令,最后打印输入内容如下:

{
  "id": 1,
  "username": "admin",
  "nickname": "测试账号",
  "avatar": "/img/avatar.png",
  "iat": 1694522590,
  "exp": 1694522592
}

# 3.2、验证 Token(异步)

TIP

  • 如果 verify 方法提供回调,则函数异步运行。
  • 如果签名验证成功,即可选的过期、受众或颁发者有效,则使用解码的有效负载调用回调
  • 如果签名验证失败,它将被调用,并返回错误
jwt.verify(token,secretOrPublicKey,[options],callback)

代码示例

const jwt = require("jsonwebtoken");
// 密钥
const secretOrPrivateKey = "abx1wseroohxsefe";
// 有效载荷
const payload = {
  id: 1,
  username: "admin",
  nickname: "测试账号",
  avatar: "/img/me_page/avatar.png",
};

const token = jwt.sign(payload, secretOrPrivateKey, {
  expiresIn: "2s",
  audience: "urn:foo",
});

// token验证,同是验证受众audience,如果受众不同也会验证失败
// 也可以不验证受众,不验证,则在第三个参数中不要添加audience字段
const decoded = jwt.verify(
  token,
  secretOrPrivateKey,
  { audience: "urn:foo" },
  (err, decoded) => {
    if (err) {
      // 验证失败,打印失败原因
      console.log(err.message);
    } else {
      // 验证成功,打印解码后的有效载荷
      console.log(decoded);
    }
  }
);

执行node jwt.js命令,最后打印输入内容如下:

{
  "id": 1,
  "username": "admin",
  "nickname": "测试账号",
  "avatar": "/img/avatar.png",
  "iat": 1694533427,
  "exp": 1694533429,
  "aud": "urn:foo"
}

# 三、实战应用:Token 身份验证应用

TIP

本小节我们主要实现 Token 身份验证,Token 身份验证流程图如下:

image-20230913183740980

我们根据上面流程图的逻辑来书写代码。

# 1、创建服务器

TIP

利用 json-server 来创建服务器,实现登录接口

// 导入json-server
const jsonServer = require("json-server");
// 返回express服务器
const server = jsonServer.create();
// body解析器中间件
server.use(jsonServer.bodyParser);

// 自定义路由 /login
server.post("/api/login", (req, res) => {
  res.send({
    id: 1,
    username: "清心",
  });
});

// 添加监听端口
server.listen(8887, () =>
  console.log("JSON Server is running at http://127.0.0.1:8887")
);

# 2、发送 Post 请求,模拟用户登录

TIP

发送 Post 请求,携带用户名和密码 。

  • 在 Vue 中通过 axios 发送请求
<script setup>
  import axios from "axios";
  async function getData() {
    // post 请求,用来向db.json的fruits字段中添加一条数据
    const reslut = await axios.post("/api/login", {
      username: "admin",
      password: "123456",
    });
  }
</script>
<template>
  <button @click="getData">登录</button>
</template>
  • 如果用 REST Client 插件来实现发送 POST 请求,代码如下:
POST http://127.0.0.1/api/login
Content-Type: application/json

#以下为模拟的请求体(JSON字符串)
{
    "username":"清心",
    "password":"123456"
}
  • 如果用 Postman 发请求,如下:

image-20231013182016970

# 3、验证用户名和密码

TIP

  • 验证用户名和密码,如果成功,则返回用户信息(去掉密码)和 Token
  • src/data/user-list.data.js中模拟一些注册过的用户信息
const userList = [
  {
    id: 1,
    username: "admin",
    password: "123456",
    nickname: "测试账号",
    avatarUrl: "/images/logo/cblogo.png",
  },
  {
    id: 2,
    username: "icoding",
    password: "123456",
    nickname: "艾编程",
    avatarUrl: "/images/logo/mlogo.png",
  },
  {
    id: 3,
    username: "arry",
    password: "123456",
    nickname: "arry",
    avatarUrl: "/images/logo/ydlogo.png",
  },
];

module.exports = () => {
  return userList; // userList一定要定义在箭头函数外面
};
  • 根据用户输入的用户名和密码,查询用户是否有注册过,如果没有注册返回,返回错误提示,如果有注册,则返回用户信息和 token
const userList = require("../data/user-list.data");
// 导入生成token的方法
const TokenService = require("../service/token");

module.exports = (req, res, next) => {
  // 获取请求体内容
  const { username, password } = req.body;
  // 获取所有用户信息
  const userListData = userList();
  // 遍历所有用户,看是否有用户名和密码匹配的用户
  const userInfo = userListData.find(
    (v) => v.username === username && v.password === password
  );

  // 如果存在该用户
  if (!userInfo) {
    res.fail(null, "登录失败,请填写正确的用户名和密码");
    return;
  }
  // 否则,将用户密码删除,然后返回用户信息和token
  // delete userInfo.password  不能直接删除,数据为引用类型在这里
  const copyUserInfo = { ...userInfo };
  delete copyUserInfo.password;
  res.success({
    token,
    userInfo: "xxx", // 模拟假的token
  });
};

# 4、生成 Token

TIP

生成 Token,并返回 Token 和 用户信息(去掉密码)

将生成 token 与 校验 token 的功能封装在src/service/token.js中,然后对外暴露

// 导入 jsonwebtoken
const jwt = require("jsonwebtoken");
// 定义密钥
const secretKey = "ibc123qxin123arry123";

// 导入需要验证的路由
const AUTH_URL = require("./config");

/**
 * 用来生成token
 * @param userInfo  登录成功后用户信息
 * @param time token过期时间 单位为秒,默认过期时间为1天
 * @returns 函数返回值为token字符串
 */
const create = (userInfo, time = 24 * 60 * 60) => {
  return jwt.sign(userInfo, secretKey, {
    expiresIn: time,
  });
};

/**
 * 认证(解析)token
 * @param token 有效的token字符串
 */
const parse = (token) => {
  // 判断是否传入token
  if (token) {
    try {
      // 认证成功,返回用户信息(有效载荷)
      return jwt.verify(token, secretKey);
    } catch (error) {
      // 认证失败,返回空内容
      return null;
    }
  }
  // 如果没有传入token,直接返回null
  return null;
};

/**
 * 判断当前路由是否需要身份验证
 * @param path 路由
 * @returns 返回值为boolean类型,false表示不需要验证,true表示需要验证
 */
const isAuthURL = (path) => {
  // 存在,则返回true,表示需要验证,false表示不需要验证
  return AUTH_URL.includes(path);
};

/**
 * 对路由身份验证(验证是否是授权)有权访问
 * @param req 请求体
 * @returns 返回值为boolean值,true表示身份认证成功,false表示身份认证失败
 */
const isAuthorized = (req) => {
  // 获取当前路由
  const path = req.path;
  // 如果不需要身份认证,直接返回true
  if (!isAuthURL(path)) {
    return true;
  }
  // 否则读取请求头中的token字段
  const token = req.headers["authorization"];
  console.log("token", token);
  // 对token请求身份认证
  const result = parse(token);
  console.log(result);
  if (result && result.username) {
    return true;
  } else {
    return false;
  }
};

module.exports = {
  create,
  parse,
  isAuthorized,
};

login.controller.js文件中导入并使用

const userList = require("../data/user-list.data");
// 导入生成token的方法
const TokenService = require("../service/token");

module.exports = (req, res, next) => {
  // 获取请求体内容
  const { username, password } = req.body;
  // 获取所有用户信息
  const userListData = userList();
  console.log(userListData);
  // 遍历所有用户,看是否有用户名和密码匹配的用户
  const userInfo = userListData.find(
    (v) => v.username === username && v.password === password
  );

  // 如果存在该用户
  if (!userInfo) {
    res.fail(null, "登录失败,请填写正确的用户名和密码");
    return;
  }
  // 否则,将用户密码删除,然后返回用户信息和token
  // delete userInfo.password  不能直接删除,数据为引用类型在这里
  const copyUserInfo = { ...userInfo };
  delete copyUserInfo.password;
  const token = TokenService.create(copyUserInfo);
  res.success({
    token,
    userInfo: copyUserInfo,
  });
};

# 5、客户端保存 Token 到 localStorage

TIP

客户端将后端返回的 Token 信息保存在 localStorage 中,以下为 vue 代码

<script setup>
  import axios from "axios";
  async function login() {
    let result;
    try {
      // post 请求,用来向db.json的fruits字段中添加一条数据
      result = await axios.post("/api/login", {
        username: "清心",
        password: "123456",
      });
    } catch (err) {
      result = null;
    }

    // 登录成功,并成功返回token
    if (result && result.data.code === 0) {
      const token = result.data.token;
      // 将后端返回的数据保证在localStorage
      localStorage.setItem("token", token);
    }
  }
</script>

<template>
  <button @click="login">登录</button>
</template>

# 6、前端路由身份校验

TIP

前端在页面跳转时,有页路由只有用户登录了才能进入,否则不允许进入。则需要对进入该路由的用户进行身份的校验。

我们可以在路由的前置导航守卫中来做路由拦截,如果该路由需要身份校验,则发送请求到后端认证身份,身份认证通过,则放行,否则不放行或跳转到首页

import { createRouter, createWebHistory } from "vue-router";
import axios from "axios";

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: "/",
      name: "home",
      component: () => import("../views/HomeView.vue"),
    },
    {
      path: "/about",
      name: "about",
      component: () => import("../views/AboutView.vue"),
      meta: { requiresAuth: true }, // 路由需要身份认证
    },
    {
      path: "/news",
      name: "news",
      component: () => import("../views/NewsView.vue"),
    },
  ],
});

// 全局前置守卫
router.beforeEach(async (to, from) => {
  // 否判断是否需要身份验证
  if (to.meta.requiresAuth) {
    // 需要身份认证,获取token
    const token = localStorage.getItem("token") || null;

    let result;
    // 如果有token,则发请求,验证身份验证通过则放行,否则回到首页 ,或不放行
    if (token) {
      // 认证token,将token发送到后台
      result = await axios.post("/api/auth", {
        token,
      });
      // 后端认证通过返回true,否则返回false
      if (result) {
        return true;
      }
    }
    return false;
  } else {
    return true;
  }
});
export default router;

身份验证接口

const TokenService = require("../service/token");
server.post("/api/auth", (req, res) => {
  // 获取token
  const { token } = req.body;
  // 对token认证
  if (TokenService.parse(token)) {
    res.success(true, "身份认证成功");
  } else {
    res.fail(false, "身份认证失败");
  }
});

# 7、受保护的 API

TIP

后端接口会针对部分受保护的 API 需要提供身份认证,认证通过才会响应正确的信息,否则直接返回 401 错误。

  • 将所有受保护的 API,存放在/src/server/config.js文件中,然后对外暴露
// 用来保存所有需要身份认证的接口
module.exports = ["/api/order", "/api/cart", "/api/getfruits"];
  • /src/server/token.js 将对接口认证的逻辑添加到 token.js 文件中
// 导入 jsonwebtoken
const jwt = require("jsonwebtoken");
// 定义密钥
const secretKey = "ibc123qxin123arry123";

// 导入需要验证的路由
const AUTH_URL = require("./config");

/**
 * 用来生成token
 * @param userInfo  登录成功后用户信息
 * @param time token过期时间 单位为秒,默认过期时间为1天
 * @returns 函数返回值为token字符串
 */
const create = (userInfo, time = 24 * 60 * 60) => {
  return jwt.sign(userInfo, secretKey, {
    expiresIn: time,
  });
};

/**
 * 认证(解析)token
 * @param token 有效的token字符串
 */
const parse = (token) => {
  // 判断是否传入token
  if (token) {
    try {
      // 认证成功,返回用户信息(有效载荷)
      return jwt.verify(token, secretKey);
    } catch (error) {
      // 认证失败,返回空内容
      return null;
    }
  }
  // 如果没有传入token,直接返回null
  return null;
};

/**
 * 判断当前路由是否需要身份验证
 * @param path 路由
 * @returns 返回值为boolean类型,false表示不需要验证,true表示需要验证
 */
const isAuthURL = (path) => {
  // 存在,则返回true,表示需要验证,false表示不需要验证
  return AUTH_URL.includes(path);
};

/**
 * 对路由身份验证(验证是否是授权)有权访问
 * @param req 请求体
 * @returns 返回值为boolean值,true表示身份认证成功,false表示身份认证失败
 */
const isAuthorized = (req) => {
  // 获取当前路由
  const path = req.path;
  // 如果不需要身份认证,直接返回true
  if (!isAuthURL(path)) {
    return true;
  }
  // 否则读取请求头中的token字段
  const token = req.headers["authorization"];
  console.log("token", token);
  // 对token请求身份认证
  const result = parse(token);
  console.log(result);
  if (result && result.username) {
    return true;
  } else {
    return false;
  }
};

module.exports = {
  create,
  parse,
  isAuthorized,
};

我们可以添加中间件拦截所有请求,然后对请求身份验证。

const TokenService = require("../service/token");
server.use((req, res, next) => {
  // 拿到请求的接口
  const result = TokenService.isAuthorized(req);
  if (result) {
    next();
  } else {
    // 401状态码,表示缺少有效身份验证凭据的未经授权的请求
    res.sendStatus(401);
  }
});
  • 当发送请求受保护的API,在请求头 Authorization 中携带 token
  • 利用 REST Client 插件来模拟发送 GET 请求,并在请求中添加 authorization 字段,该字段携带 token
GET http://127.0.0.1:8887/api/user
authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoxLCJ1c2VybmFtZSI6Iua4heW_gyIsImlhdCI6MTY5NDU0MDgzOCwiZXhwIjoxNjk0NTQwODk4fQ.VErt-ddOC-TVbjdgz6j85Pcz794gZG5TjbFht_Nt6-w
上次更新时间: 10/16/2023, 1:52:39 AM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X