使用React和zustand实现JWT登录鉴权全流程解析

118 阅读3分钟

引言:现代Web应用的认证机制

在现代Web应用中,用户认证是一个至关重要的功能。JSON Web Token(JWT)作为一种轻量级、跨平台的认证方案,已成为前后端分离架构中的首选方案。本文将详细介绍如何使用React和zustand状态管理库实现完整的JWT登录鉴权流程。

核心概念解析

JWT是什么?

JWT是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。它由三部分组成:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

为什么选择zustand?

zustand是一个轻量级的React状态管理库,相比Redux:

  • 更简单的API
  • 更小的包体积
  • 无需额外的Provider包裹
  • 天然支持异步操作

项目实现详解

1. 全局状态管理(zustand)

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

export const useUserStore = create(set => ({
  user: null, // 用户信息
  isLogin: false, // 登录状态
  
  // 登录方法
  login: async ({username="", password=""}) => {
    const res = await doLogin({username, password});
    const {token, data: user} = res.data;
    localStorage.setItem('token', token); // 存储token
    set({ user, isLogin: true }) // 更新状态
  },
  
  // 注销方法
  logout: () => {
    localStorage.removeItem('token'); // 清除token
    set({ user: null, isLogin: false }) // 重置状态
  }
}))

2. API请求配置(axios拦截器)

// api/config.js
import axios from 'axios'

axios.defaults.baseURL = 'http://localhost:5173/api'

// 请求拦截器
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token') || ''
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

// 响应拦截器
axios.interceptors.response.use(res => {
  return res
})

export default axios

3. 登录页面实现

// views/Login/index.jsx
import { useRef } from 'react';
import { useUserStore } from '../../store/user'
import { useNavigate } from 'react-router-dom'

const Login = () => {
  const usernameRef = useRef();
  const passwordRef = useRef();
  const { login } = useUserStore();
  const navigate = useNavigate();
  
  const handleLogin = (e) => {
    e.preventDefault();
    const username = usernameRef.current.value;
    const password = passwordRef.current.value;
    
    if (!username || !password) {
      alert("请输入用户名或密码");
      return;
    }
    
    login({username, password});
    setTimeout(() => {
      navigate('/') // 登录成功后跳转首页
    }, 1000)
  }
  
  return (
    <form onSubmit={handleLogin}>
      {/* 登录表单 */}
    </form>
  )
}

4. 路由守卫实现

// components/RequireAuth/index.jsx
import { useUserStore } from '../../store/user'
import { useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'

const RequireAuth = ({children}) => {
  const {isLogin} = useUserStore()
  const navigate = useNavigate()
  const { pathname } = useLocation()
  
  useEffect(() => {
    if (!isLogin) {
      navigate('/login', { state: { from: pathname } }) // 记录来源页面
    }
  }, [isLogin])
  
  return <>{children}</>
}

5. 受保护路由配置

// App.jsx
const App = () => (
  <>
    <NavBar />
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path='/' element={<Home />} />
        <Route path='/login' element={<Login />} />
        <Route path='/pay' element={
          <RequireAuth>
            <Pay />
          </RequireAuth>
        } />
      </Routes>
    </Suspense>
  </>
)

6. 导航栏状态展示

// components/NavBar/index.jsx
import { Link } from 'react-router-dom'
import { useUserStore } from '../../store/user'

const NavBar = () => {
  const { isLogin, user, logout } = useUserStore();
  
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/pay">Pay</Link>
      
      {isLogin ? (
        <>
          <span>欢迎:{user.username}</span>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <Link to="/login">Login</Link>
      )}
    </nav>
  )
}

JWT工作流程解析

  1. 用户登录:提交凭证到服务器
  2. 服务器验证:验证凭证有效性
  3. 生成JWT:服务器生成token并返回
  4. 客户端存储:前端将token存储在localStorage
  5. 后续请求:在请求头中添加Authorization字段
  6. 服务器验证:服务器验证token有效性
  7. 返回数据:验证通过后返回请求数据

安全最佳实践

  1. 使用HTTPS:防止token在传输过程中被截获
  2. 设置合理有效期:避免token长期有效
  3. 避免敏感数据:不要在payload中存储敏感信息
  4. 使用强密钥:确保签名密钥足够复杂
  5. 启用HttpOnly Cookie:更安全的token存储方式(可选)

常见问题解决方案

1. token过期处理

// 在响应拦截器中处理
axios.interceptors.response.use(
  response => response,
  error => {
    if (error.response.status === 401) {
      // token过期,跳转到登录页
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

2. 页面刷新状态丢失

// 应用初始化时检查token
const useUserStore = create(set => ({
  // ...
  init: () => {
    const token = localStorage.getItem('token');
    if (token) {
      // 验证token有效性
      set({ isLogin: true });
    }
  }
}));

// 在App组件中调用
useEffect(() => {
  useUserStore.getState().init();
}, []);

3. 路由守卫改进

// 更完善的路由守卫
const RequireAuth = ({ children, roles }) => {
  const { isLogin, user } = useUserStore();
  
  if (!isLogin) {
    // 处理未登录
  }
  
  if (roles && !roles.includes(user.role)) {
    // 处理权限不足
  }
  
  return children;
}

性能优化建议

  1. 按需加载:使用React.lazy和Suspense实现组件懒加载
  2. token验证缓存:避免每次请求都验证token
  3. 请求节流:对频繁的API请求进行节流控制
  4. 状态持久化:使用zustand-middleware持久化部分状态
  5. 错误边界:添加错误边界组件捕获异常

总结

本文详细介绍了使用React和zustand实现JWT登录鉴权的完整流程,涵盖了:

  1. zustand全局状态管理
  2. axios拦截器配置
  3. 路由守卫实现
  4. 安全最佳实践
  5. 常见问题解决方案