引言
在现代 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 工作流程
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 模拟
无需编写后端代码,即可测试前端逻辑
步骤:
-
访问 Apifox 并创建项目。
-
新建接口
/api/login,设置响应体:json 深色版本 { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx.xxxxx" } -
在前端调用
fetch('/api/login'),Apifox 会自动返回模拟数据。 -
可设置登录成功/失败场景,测试不同状态。
前端开发独立于后端,提高开发效率。
八、JWT 的优缺点总结
| 优点 | 缺点 |
|---|---|
| 无状态,适合分布式系统 | Token 一旦签发无法主动失效(除非用黑名单) |
| 自包含,Payload 可携带用户信息 | 不适合存储敏感信息(可被 base64 解码) |
| 跨域支持好 | 过期时间固定,灵活性差 |
| 易于移动端使用 | 需要妥善保管 secret |
九、最佳实践建议
| 场景 | 推荐做法 |
|---|---|
| Token 存储 | 使用 httpOnly Cookie 更安全(防 XSS) |
| 刷新机制 | 使用 Refresh Token 机制 |
| 权限控制 | 结合 level 或 roles 字段做细粒度控制 |
| 错误处理 | 401 跳转登录,403 提示无权限 |
| 安全性 | secret 使用环境变量,定期更换 |
结语
JWT + Zustand + React Router 的组合,为现代 React 应用提供了一套完整、轻量、高效的用户鉴权方案。通过 apifox 等工具进行 API 模拟,可以实现前后端并行开发,大幅提升开发效率。
掌握这套技术栈,你将能够构建出安全、可扩展、用户体验良好的 Web 应用。