在现代化Web应用中,安全可靠的用户认证系统是必备基础。本文将带你深入理解JWT技术,并手把手实现一个完整的React登录鉴权系统。
一、JWT:现代Web认证的利器
什么是JWT?
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全传输信息作为JSON对象。它由三部分组成,用点号分隔:
- Header:包含算法和令牌类型
- Payload:携带的用户数据(声明)
- Signature:验证消息完整性的签名
一个典型的JWT看起来像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT的优势
- 无状态:服务器不需要存储会话信息
- 跨域支持:天然支持CORS跨域请求
- 自包含性:所有必要信息都包含在令牌中
- 可扩展性:轻松添加自定义声明
- 安全性:基于数字签名防止篡改
二、实战:React + JWT登录鉴权系统
项目结构概览
src/
├── App.jsx # 主应用组件
├── authStore.js # Zustand状态管理
├── api/
│ └── login.js # 登录API模拟
└── components/
├── LoginForm.jsx # 登录表单组件
└── Dashboard.jsx # 登录后仪表盘
1. 状态管理:使用Zustand
// src/authStore.js
import { create } from 'zustand';
const useAuthStore = create((set) => ({
isLogin: false,
user: null,
token: null,
login: (userData, token) => set({
isLogin: true,
user: userData,
token
}),
logout: () => set({
isLogin: false,
user: null,
token: null
}),
}));
export default useAuthStore;
2. 增强Mock登录API(返回JWT)
// src/api/login.js
import jwt from 'jsonwebtoken';
const users = [
{ id: 1, username: 'admin', password: 'admin123', role: 'admin' },
{ id: 2, username: 'user', password: 'user123', role: 'user' }
];
const SECRET_KEY = 'your_secret_key_here';
export default [
{
url: '/api/login',
method: 'post',
timeout: 1000,
response: ({ body }) => {
const { username, password } = body;
const user = users.find(u =>
u.username === username && u.password === password
);
if (!user) {
return { code: 401, message: '用户名或密码错误' };
}
// 生成JWT(有效期为1小时)
const token = jwt.sign(
{ userId: user.id, role: user.role },
SECRET_KEY,
{ expiresIn: '1h' }
);
return {
code: 200,
data: {
user: {
id: user.id,
username: user.username,
role: user.role
},
token
}
};
}
}
];
3. 登录表单组件实现
// src/components/LoginForm.jsx
import { useState } from 'react';
import useAuthStore from '../authStore';
const LoginForm = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const login = useAuthStore(state => state.login);
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const result = await response.json();
if (result.code === 200) {
// 保存到状态管理
login(result.data.user, result.data.token);
// 存储到localStorage
localStorage.setItem('authToken', result.data.token);
localStorage.setItem('user', JSON.stringify(result.data.user));
} else {
setError(result.message || '登录失败');
}
} catch (err) {
setError('网络请求失败');
}
};
return (
<form onSubmit={handleSubmit} className="login-form">
<h2>用户登录</h2>
{error && <div className="error">{error}</div>}
<div className="form-group">
<label>用户名</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>密码</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" className="login-btn">登录</button>
</form>
);
};
export default LoginForm;
4. 主应用组件整合
// src/App.jsx
import { useEffect } from 'react';
import useAuthStore from './authStore';
import LoginForm from './components/LoginForm';
import Dashboard from './components/Dashboard';
function App() {
const { isLogin, user, login } = useAuthStore();
// 检查本地存储的登录状态
useEffect(() => {
const token = localStorage.getItem('authToken');
const userData = JSON.parse(localStorage.getItem('user'));
if (token && userData) {
login(userData, token);
}
}, [login]);
return (
<div className="app">
<header>
<h1>JWT认证系统</h1>
{isLogin && <div className="user-info">欢迎, {user.username}</div>}
</header>
<main>
{isLogin ? <Dashboard /> : <LoginForm />}
</main>
<footer>
<p>© 2023 JWT认证演示</p>
</footer>
</div>
);
}
export default App;
5. 请求拦截器(添加JWT)
// 在应用入口文件添加
import axios from 'axios';
axios.interceptors.request.use(config => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, error => {
return Promise.reject(error);
});
三、JWT安全最佳实践
-
使用HTTPS:防止令牌在传输中被窃取
-
设置合理有效期:缩短令牌生命周期
-
存储安全:
- 避免在localStorage存储敏感信息
- 考虑使用HttpOnly Cookie
-
令牌刷新机制:
// 刷新令牌示例 const refreshToken = async () => { const response = await fetch('/api/refresh-token', { method: 'POST', credentials: 'include' // 发送刷新令牌的Cookie }); const { token } = await response.json(); localStorage.setItem('authToken', token); return token; }; -
黑名单处理:对于需要提前失效的令牌,使用黑名单机制
四、JWT vs Session:如何选择?
| 特性 | JWT | Session |
|---|---|---|
| 状态管理 | 无状态 | 有状态 |
| 扩展性 | 高(天然支持分布式) | 需要共享存储方案 |
| 存储位置 | 客户端 | 服务器 |
| 跨域支持 | 容易 | 需要额外配置 |
| 安全性 | 依赖签名/加密强度 | 依赖Cookie安全配置 |
| 令牌大小 | 较大 | 较小(仅Session ID) |
五、总结
JWT作为现代Web认证的标准方案,通过本文我们:
- 理解了JWT的结构和工作原理
- 实现了React + Zustand + JWT的完整登录鉴权流程
- 掌握了JWT的安全实践和最佳方案
- 了解了JWT与传统Session的差异