安全访问受保护的资源:基于 JSON Web Token (JWT) 的身份认证机制

170 阅读6分钟

1. 开始之前的准备

我们先在新创建好的react空项目下进行配置环境,在终端输入:

pnpm i zustand
pnpm i vite-mock-plugin-mock //在 Vite 项目中模拟 API 接口的包  

接着我们在 vite.config.js 文件中配置 Vite 插件,这是主要用于在 Vite 项目中启用 Mock 功能,方便在开发阶段模拟后端接口数据。

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; // 支持React项目的Vite插件
import { viteMockServe } from 'vite-plugin-mock'; // 用于模拟API请求的插件
import path from 'path'; // Node.js路径处理模块,用于解析文件路径

添加:

// 使用defineConfig函数定义Vite的配置
export default defineConfig({
  // 配置Vite的插件
  plugins: [
    // 使用@vitejs/plugin-react插件,支持React项目
    react(),
    // 配置vite-plugin-mock插件
    viteMockServe({
      // 指定Mock数据文件的存放路径为src/mock目录
      mockPath: 'src/mock',
      // 在开发环境中启用Mock服务
      localEnabled: true,
    })
  ],
  // 配置模块解析选项
  resolve: {
    // 配置路径别名
    alias: {
      // 将@映射到项目的src目录
      '@': path.resolve(__dirname, 'src')
    }
  }
})

配置登录接口模拟(Mock)

随后我们在src下创建mock文件夹,放置文件login.js

// 导出一个包含模拟接口配置的数组
export default [
    {
        // 定义接口的URL路径,客户端需要访问此路径来触发该模拟接口
        url: '/api/login',
        // 指定接口的请求方法为POST,与客户端登录请求的方法保持一致
        method: 'post',
        // 设置模拟请求的延迟响应时间为2秒,用于测试前端的加载状态
        timeout: 2000, 
        // 接口响应的处理函数,接收请求(req)和响应(res)对象作为参数
        response: (req, res) => {
            // 从请求体中解构提取用户名和密码字段
            const {username, password} = req.body;
            // 登录验证逻辑:检查用户名和密码是否同时符合预期
            if (username !== 'admin' || password !== '123456') {
                // 返回错误响应对象,包含错误码和错误信息
                return {
                    code: 1, // 自定义错误码,表示登录失败
                    message: '用户名或密码错误' // 明确的错误提示信息
                }
            }
            // 若验证通过(用户名和密码均正确),则返回成功响应
            return {
                username, // 返回客户端提交的用户名
                password  // 返回客户端提交的密码(实际应返回token等安全凭证)
            }
        }
    }
]

使用 Apifox 发送请求

Apifox 可以方便地发起 HTTP、HTTPS 等多种协议的请求,设置请求头、请求体等参数,并查看响应结果,这和 Linux 下的curl命令功能类似 。curl是一个利用 URL 语法在命令行下工作的文件传输工具,也能用于发送各种 HTTP 请求(比如 GET、POST 等),并获取服务器返回的响应。

步骤如下:

(1. 创建项目:打开 Apifox,点击左侧搜索框旁边的 “+” 号按钮,创建一个新的项目。 这里我们直接选择默认创建好的"个人项目"。

微信截图_20250723212044.png

(2. 新建接口:在项目中点击 “新建接口” 按钮,填写接口的基本信息。将接口名称设置为 “登录接口”,请求方法选择 “POST”,请求 URL 填写为 “/api/login”。

微信截图_20250723212338.png

这里我们根据项目地址输入 http://localhost:5173/api/login

(3. 设置请求体:在接口的 “请求体” 部分,选择 Body 部分和 JSON 格式,同时设置请求体的结构,这里我们就用{"username":"admin","password":"123456"},表示需要传入用户名和密码。

微信截图_20250723212714.png

(4. 发送请求:点击接口页面中的 “发送” 按钮,Apifox 会向设置的 “/api/login” 接口发送 POST 请求,并显示响应结果。你可以在响应区域查看返回的内容。

微信截图_20250723212921.png 当接口请求的状态码为 200时,表示请求成功。其中响应时间为 2.02 s,数据大小为 40 B

响应的 Body 内容也是 JSON 格式,返回了 username 和 password 字段,与请求中的字段值一致。

此时说明我们刚刚配置的模拟接口已经成功工作,能够正确接收请求并返回相应的数据。

2. 实现基于 JSON Web Token (JWT) 的身份认证机制

我们先安装 jsonwebtoken 库,以便在项目中使用 JWT 功能。

pnpm i jsonwebtoken

再来到login.js中添加:

// 从 jsonwebtoken 库中导入 sign 函数,用于生成 JWT
import jwt from 'jsonwebtoken';

// 解构赋值获取 sign 函数,用于生成 JWT 令牌
const { sign } = jwt;

// 安全密钥,用于签署 JWT 令牌
const secret = '!&124coddefgg';

  // 调用 sign 函数生成 JWT 令牌
  const token = jwt.sign(
    {
      // 存储在 JWT 中的用户信息(Payload 部分)
      user: {
        id: "001",           // 用户唯一标识
        username: "admin"    // 用户名
      }
    },
    secret,                 // 用于签名的密钥(应从环境变量获取)
    {
      expiresIn: 86400      // 令牌有效期(单位:秒),此处为 24 小时
    }
  
  // 返回包含令牌的对象(可扩展其他属性)
  return {
    token, // 生成的 JWT 令牌
    ...
  };
};

此时我们返回 apifox 生成 token :

微信截图_20250723223733.png

在请求头中添加身份验证信息,用于验证客户端是否有权限访问该接口

{
        url: '/api/user',
        method: 'get',
        response: (req, res) => {
            // 用户端 token headers 
            const token = req.headers["authorization"].split(' ')[1];
            console.log(token)
            try {
                const decode = jwt.decode(token, secret);
                console.log(decode)
                return {
                    code: 0,
                    data: decode.user
                }
            } catch(err) {
                return {
                    code: 1,
                    message: 'Invalid token'
                }
            }
        }
    }

在 src/api 目录下创建两个核心文件来共同构成项目的API请求系统

先安装axios

pnpm i axios

config.js

// 导入 axios HTTP 客户端库
import axios from 'axios';

axios.defaults.baseURL = 'http://localhost:5177/api';

// 请求拦截器:在每个请求发送前执行用于自动添加 JWT 令牌到请求头,实现身份验证
axios.interceptors.request.use(config => {
    // 从本地存储中获取用户令牌(如果存在)
    const token = localStorage.getItem('token') || "";
    // 如果令牌存在,则添加到请求头的 Authorization 字段
    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
});
// 响应拦截器:在每个响应返回后执行
axios.interceptors.response.use(res => {
    // 打印响应标识(实际项目中可移除或改为更详细的日志)
    console.log('|||||||');
    // 直接返回响应数据,不做其他处理
    return res;
});
// 导出配置好的 axios 实例供项目使用
export default axios;

这段代码配置了 Axios HTTP 客户端的全局设置和拦截器,使得项目中所有 API 请求都会自动携带身份令牌,同时便于统一处理响应。

user.js

import axios from './config';
export const getUser = () => {
    return axios.get('/user');
}
export const doLogin = (data) => {
    return axios.post('/login', data);
} 

在这里代码封装了两个与用户认证相关的 API 请求函数:

  1. getUser() :调用已配置好的 axios 实例发送 GET 请求到 /user 接口,由于 axios 配置了请求拦截器,会自动携带本地存储中的 JWT 令牌,用于获取当前登录用户的详细信息。
  2. doLogin(data) :调用 axios 发送 POST 请求到 /login 接口,接收表单数据(如用户名 / 密码)作为参数并传递给后端,用于用户登录认证,成功后通常会返回新的 JWT 令牌。

发送 GET 请求

微信截图_20250724114310.png

当出现 code: 0时,表示请求成功。

你大概率会注意到 token 前面的 Bearer ,它是什么?

持有者(Bearer) :这里指持有令牌的客户端(如浏览器、APP)。服务器通过验证令牌的有效性,确认 “持有者” 是否有权限访问资源。

整个过程简单概括就是:用 secret 密钥签名生成的 JWT 令牌,会以 Bearer ${token} 的格式,通过 HTTP 请求头的 Authorization 字段,从客户端(持有者)传递到服务器,用于身份验证

小结

在整个流程中,我们通过环境搭建→模拟接口→JWT生成→请求拦截→身份验证五步骤,确保前端能安全访问受保护的资源。

  1. 环境准备 :用pnpm安装zustand、vite-mock-plugin-mock(模拟API)、jsonwebtoken(生成JWT)和axios(HTTP请求)等依赖。

  2. Vite配置 :在vite.config.js中启用React插件和Mock服务,设置Mock数据路径为src/mock,并配置@别名指向src目录。

  3. Mock接口 :在src/mock/login.js中定义登录接口,模拟用户验证逻辑(用户名admin/密码123456),成功后生成JWT令牌(包含用户信息,有效期24小时)。

  4. API系统 :在src/api/config.js中配置Axios拦截器(自动添加JWT到请求头),并在user.js中封装登录和获取用户信息的API函数。

  5. 测试验证 :用Apifox工具测试登录接口获取JWT,再测试带令牌的GET请求,验证身份认证机制正常工作。

在下篇文章中,我们将利用 zustand , 单页面SPA , 受控组件,懒加载等相关技术搭建一个简单的用户登录支付的 React 项目。