JWT 登录鉴权与 Zustand 全局状态管理:构建安全的 React 应用

179 阅读5分钟

引言

在现代 Web 开发中,用户身份认证和权限控制是每个应用的核心功能。随着前后端分离架构的普及,传统的 Session-Cookie 模式逐渐被 JWT(JSON Web Token) 所替代。结合 React、Zustand 状态管理库以及 JWT 技术,深入讲解如何实现一个完整的登录鉴权流程,并通过 apifox 进行 API 模拟,无需编写前端页面即可测试接口。


一、为什么需要登录鉴权?

HTTP 协议本身是无状态的,服务器无法记住用户是谁。因此我们需要一种机制来“告诉服务器:我是谁”。

传统方案:Session + Cookie

  • 用户登录后,服务器创建一个 session,并生成唯一的 sid(Session ID)。
  • 服务器通过 Set-Cookie 将 sid 下发给浏览器。
  • 浏览器后续请求自动携带 Cookie,服务器根据 sid 查找对应的用户信息。

优点:简单、成熟。
缺点:依赖服务器存储 session,难以扩展(不适合分布式系统)。


二、现代方案:JWT(JSON Web Token)

1. 什么是 JWT?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络间安全传输信息,常用于身份验证和授权 1 2 。它由 Header(头部)、Payload(负载)和 Signature(签名)三部分组成,以点分隔。JWT 自包含、紧凑,可通过 URL、POST 参数或 HTTP 头部传输,无状态,适合分布式系统。

一个典型的 JWT 字符串如下:

xxxxx.yyyyy.zzzzz

由三部分组成:

部分内容
Header算法类型(如 HS256)
Payload用户数据(如 {id: 1, username: 'alice', level: 2}
Signature签名(使用 secret 加密前两部分)

2. JWT 工作流程

image.png


3. 前端如何使用 JWT?

  • 登录成功后,保存 token(通常存入 localStorage 或 sessionStorage)。
  • 每次请求 API 时,在请求头中添加:
Authorization: Bearer <token>
  • 使用 axios 拦截器统一设置:
// axios.interceptor.js
import axios from 'axios';

const api = axios.create({
  baseURL: '/api',
});

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

export default api;

三、使用 jsonwebtoken 库进行 JWT 操作

安装

pnpm add jsonwebtoken

注意:jsonwebtoken 主要在 Node.js 后端使用,前端一般只负责发送 token,不进行签发或验证。

后端示例(Node.js)

// server.js
import jwt from "jsonwebtoken";
//安全性 编码的时候加密
//解码的时候用于解密
//加盐(增加安全性)
const secret = '!&124ferkj';

//login 模块 mock
export default [
    {
        url:'/api/login',
        method:'post',
        timeout:2000,//请求耗时
        response:(req,res)=>{
            //req,username,password
            const {username,password} = req.body;
            if(username !== 'admin' || password !== '123456'){
                return {
                    code:1,
                    message:'用户名或密码错误'
                }
            }
            //json用户数据
            const token = jwt.sign({
                user:{
                    id:"001",
                    username:"admin",
                }
            },secret,{
                expiresIn:86400,//token过期时间
            })
            console.log(token,'///');
            //生成token颁发令牌
            return {
                token,
                username,
                password
            }
        }
    },


    {
        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'
                }
            }
        }
    }


]

四、前端状态管理:Zustand 存储用户状态

我们使用 Zustand 来管理用户的登录状态和基本信息。

创建用户状态 store

// store/useUserStore.js
import {
    create
}from 'zustand'
import {
    doLogin
} from '../api/user'

export const useUserStore = create((set=>({
    user:null,//用户信息
    isLogin:false,//是否登录
    login: async({username="",password=""})=>{
        const data = await doLogin({username,password})
        console.log(data)
        const {token, data:user} = data.data;
        localStorage.setItem('token',token);
        set({
            user,
            isLogin:true
        })
    },
    logout:()=>{
        localStorage.removeItem('token');
        set({
            user:null,
            isLogin:false
        })
    }
})))

五、登录流程实现

1. 登录页面(受控组件)

// pages/LoginPage.jsx
import { useState } from 'react';
import useUserStore from '../store/useUserStore';
import api from '../utils/api';

function LoginPage() {
  const [form, setForm] = useState({ username: '', password: '' });
  const { login } = useUserStore();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const res = await api.post('/login', form);
      const { token } = res.data;
      
      // 解码 payload(仅用于展示,不用于安全判断)
      const payload = JSON.parse(atob(token.split('.')[1]));
      
      login(payload, token);
      alert('登录成功!');
    } catch (error) {
      alert('登录失败');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={form.username}
        onChange={(e) => setForm({ ...form, username: e.target.value })}
        placeholder="用户名"
      />
      <input
        type="password"
        value={form.password}
        onChange={(e) => setForm({ ...form, password: e.target.value })}
        placeholder="密码"
      />
      <button type="submit">登录</button>
    </form>
  );
}

六、路由守卫:保护私有页面

使用 react-router-dom 实现路由权限控制。

// components/ProtectedRoute.jsx
import { Navigate } from 'react-router-dom';
import useUserStore from '../store/useUserStore';

function ProtectedRoute({ children }) {
  const { isLogin } = useUserStore();

  if (!isLogin) {
    return <Navigate to="/login" replace />;
  }

  return children;
}

export default ProtectedRoute;

路由配置

// App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import LoginPage from './pages/LoginPage';
import Dashboard from './pages/Dashboard';
import ProtectedRoute from './components/ProtectedRoute';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        <Route
          path="/dashboard"
          element={
            <ProtectedRoute>
              <Dashboard />
            </ProtectedRoute>
          }
        />
      </Routes>
    </BrowserRouter>
  );
}

七、使用 Apifox 进行 API 模拟

无需编写后端代码,即可测试前端逻辑

步骤:

  1. 访问 Apifox 并创建项目。

  2. 新建接口 /api/login,设置响应体:

    json
    深色版本
    {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx.xxxxx"
    }
    
  3. 在前端调用 fetch('/api/login'),Apifox 会自动返回模拟数据。

  4. 可设置登录成功/失败场景,测试不同状态。

前端开发独立于后端,提高开发效率。


八、JWT 的优缺点总结

优点缺点
无状态,适合分布式系统Token 一旦签发无法主动失效(除非用黑名单)
自包含,Payload 可携带用户信息不适合存储敏感信息(可被 base64 解码)
跨域支持好过期时间固定,灵活性差
易于移动端使用需要妥善保管 secret

九、最佳实践建议

场景推荐做法
Token 存储使用 httpOnly Cookie 更安全(防 XSS)
刷新机制使用 Refresh Token 机制
权限控制结合 level 或 roles 字段做细粒度控制
错误处理401 跳转登录,403 提示无权限
安全性secret 使用环境变量,定期更换

结语

JWT + Zustand + React Router 的组合,为现代 React 应用提供了一套完整、轻量、高效的用户鉴权方案。通过 apifox 等工具进行 API 模拟,可以实现前后端并行开发,大幅提升开发效率。

掌握这套技术栈,你将能够构建出安全、可扩展、用户体验良好的 Web 应用。