初识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只是前端安全领域的冰山一角,未来还有更多有趣的技术等待我去探索。