我的JWT登录鉴权学习之旅,了解React基本项目构建全流程

71 阅读3分钟

初识JWT:从困惑到清晰

作为一名前端程序员,我一直对用户认证这个话题充满好奇。当我第一次听到JWT(JSON Web Token)这个词时,脑子里充满了疑问:它和传统的Cookie认证有什么区别?为什么现在这么多项目都在用它?带着这些问题,我开始了在 jwt-demo 项目中的探索之旅。

记得刚开始配置项目时,我对着 src/api/config.js 里的基础URL发呆了好久。"为什么要单独把API配置抽离出来?"后来才明白,这是为了统一管理请求地址,特别是在处理JWT的baseURL时会非常方便。这种代码组织方式让我感受到了工程化思维的魅力。

攻克JWT的"三重门"

1. 登录接口的实现:第一次握手

我的第一个挑战是实现登录功能。打开 src/api/user.js 文件,我写下了第一个登录请求函数:

import axios from 'axios';
import config from './config';

export const login = async (username, password) => {
  const response = await axios.post(`${config.baseURL}/login`, {
    username,
    password
  });
  // 保存token到本地存储
  localStorage.setItem('token', response.data.token);
  return response.data;
};

当我第一次成功获取到JWT令牌并将其存储在localStorage中时,那种喜悦难以言表!就像拿到了一把通往系统内部的钥匙。

2. 状态管理:全局状态的艺术

接下来,我需要在整个应用中共享用户登录状态。项目中使用的是Zustand,这是我第一次接触这个轻量级状态管理库。在 src/store/user.js 中,我定义了用户状态:

import create from 'zustand';
import { getProfile } from '../api/user';

export const useUserStore = create((set) => ({
  userInfo: null,
  isLoading: false,
  error: null,
  // 获取用户信息
  fetchUserProfile: async () => {
    set({ isLoading: true });
    try {
      const data = await getProfile();
      set({ userInfo: data, isLoading: false });
    } catch (err) {
      set({ error: err.message, isLoading: false });
    }
  },
  // 登出
  logout: () => {
    localStorage.removeItem('token');
    set({ userInfo: null });
  }
}));

当我在组件中通过 useUserStore 轻松获取用户信息时,我被这种简洁的API设计深深吸引。相比传统的Redux,Zustand的学习曲线低了很多,却同样强大。

3. 请求拦截:隐形的守护者

最让我感到神奇的是请求拦截器的实现。在 src/api/config.js 中,我添加了如下代码:

import axios from 'axios';

const config = {
  baseURL: import.meta.env.VITE_API_URL || '/api',
};

const api = axios.create(config);

// 请求拦截器添加token
api.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

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

export default config;
export { api };

当我看到每次请求都会自动带上Authorization头,而当token过期时又会自动跳转到登录页,我感受到了前端工程化的精妙之处。这种"一劳永逸"的解决方案,让我对前端架构有了新的认识。

路由守卫:保护应用的安全屏障

在学习过程中,我发现路由保护是实现鉴权不可或缺的一环。在 src/components/RequireAuth/index.jsx 中,我实现了一个高阶组件:

import { Navigate } from 'react-router-dom';
import { useUserStore } from '../../store/user';

const RequireAuth = ({ children }) => {
  const { userInfo, isLoading } = useUserStore();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  // 如果没有用户信息且没有token,重定向到登录页
  if (!userInfo && !localStorage.getItem('token')) {
    return <Navigate to="/login" replace />;
  }

  return children;
};

export default RequireAuth;

然后在路由配置中使用它:

<Route path="/pay" element={<RequireAuth><Pay /></RequireAuth>} />

当我尝试直接访问需要登录的页面时,系统自动将我重定向到登录页,那一刻我感受到了作为开发者掌控整个应用流程的成就感。

模拟数据:前后端分离的桥梁

在后端接口还没 ready 的情况下,我使用了 mock/login.js 来模拟登录数据:

export default [
  {
    url: '/api/login',
    method: 'post',
    response: ({ body }) => {
      if (body.username === 'admin' && body.password === '123456') {
        return {
          code: 200,
          message: '登录成功',
          data: {
            token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', // 模拟JWT token
            user: {
              id: 1,
              username: 'admin',
              nickname: '管理员'
            }
          }
        };
      }
      return {
        code: 401,
        message: '用户名或密码错误'
      };
    }
  }
];

通过Vite的mock功能,我可以独立开发前端功能,不需要等待后端接口就绪。这种前后端分离的开发模式极大地提高了我的工作效率。

结语:

回顾整个JWT学习过程,从最初的困惑到现在能够熟练运用,我不仅掌握了一项实用技能,更重要的是培养了解决问题的能力。每当我看到自己实现的登录系统安全、稳定地运行时,那种自豪感油然而生。

技术学习就像一场马拉松,不在于起跑时有多快,而在于持续前进的毅力。JWT只是前端安全领域的冰山一角,未来还有更多有趣的技术等待我去探索。