以下是使用Axios实现用户登录、注册功能并处理JWT令牌的完整方案,结合前后端实现和关键代码解析:
一、核心实现步骤环境搭建
后端:Express.js + 必要中间件(bcryptjs/jsonwebtoken/cors)
前端:React + React Router + Axios
JWT认证流程
注册:密码哈希存储 → 登录:验证密码→签发令牌 → 前端存储令牌 → 访问受保护路由时携带令牌
二、后端API实现(关键代码)// backend/server.jsconst express = require('express');const bcrypt = require('bcryptjs');const jwt = require('jsonwebtoken');const cors = require('cors');const app = express();app.use(cors());app.use(express.json());const SECRET_KEY = 'your-secret-key';let users = []; // 内存数据库(生产环境应使用真实数据库)// 注册接口app.post('/register', (req, res) => { const { username, password } = req.body; if (users.some(u => u.username === username)) { return res.status(400).send('User already exists'); } const hashedPassword = bcrypt.hashSync(password, 8); users.push({ username, password: hashedPassword }); res.send('Registration successful');});// 登录接口(返回JWT)app.post('/login', (req, res) => { const { username, password } = req.body; const user = users.find(u => u.username === username); if (!user || !bcrypt.compareSync(password, user.password)) { return res.status(401).send('Invalid credentials'); } const token = jwt.sign( { username }, SECRET_KEY, { expiresIn: '1h' } ); res.json({ token });});// 受保护路由app.get('/protected', (req, res) => { const token = req.headers['authorization']?.split(' ')[1]; if (!token) return res.sendStatus(401); jwt.verify(token, SECRET_KEY, (err, decoded) => { if (err) return res.sendStatus(403); res.json({ message: `Hello ${decoded.username}`, data: 'Protected content' }); });});app.listen(5000, () => console.log('Server running on port 5000'));三、前端实现(React + Axios)1. Axios实例配置(推荐)// src/api.jsimport axios from 'axios';const api = axios.create({ baseURL: 'http://localhost:5000'
, timeout: 5000});// 请求拦截器:自动添加JWTapi.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config;});export default api;2. 认证上下文管理// src/auth.jsimport React, { createContext, useContext, useState } from 'react';const AuthContext = createContext();export const AuthProvider = ({ children }) => { const [auth, setAuth] = useState(!!localStorage.getItem('token')); const login = (token) => { localStorage.setItem('token', token); setAuth(true); }; const logout = () => { localStorage.removeItem('token'); setAuth(false); }; return ( <AuthContext.Provider value={{ auth, login, logout }}> {children} </AuthContext.Provider> );};export const useAuth = () => useContext(AuthContext);3. 登录组件实现// src/Login.jsimport React, { useState } from 'react';import { useHistory } from 'react-router-dom';import api from './api';import { useAuth } from './auth';const Login = () => { const [form, setForm] = useState({ username: '', password: '' }); const [error, setError] = useState(''); const { login } = useAuth(); const history = useHistory(); const handleSubmit = async (e) => { e.preventDefault(); try { const res = await api.post('/login', form); login(res.data.token); history.push('/protected'); } catch (err) { setError(err.response?.data || 'Login failed'); } }; return ( <form onSubmit={handleSubmit}> <input value={form.username} onChange={e => setForm({...form, username: e.target.value})} placeholder="Username" /> <input type="password" value={form.password} onChange={e => setForm({...form, password: e.target.value})} placeholder="Password" /> <button type="submit">Login</button> {error && <p style={{color: 'red'}}>{error}</p>} </form> );};4. 受保护路由实现// src/Protected.jsimport React from 'react';import { Redirect } from 'react-router-dom';import { useAuth } from './auth';import api from './api';const Protected = () => { const { auth, logout } = useAuth(); const [data, setData] = useState(null); React.useEffect(() => { const fetchData = async () => { try { const res = await api.get('/protected'); setData(res.data); } catch (err) { if (err.response?.status === 401) logout(); } }; fetchData(); }, [logout]); if (!auth) return <Redirect to="/login" />; return ( <div> <h2>Protected Content</h2> {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : 'Loading...'} </div> );};四、关键安全注意事项令牌存储
使用httpOnly Cookie更安全(需配合服务端设置)
当前方案使用localStorage,需防范XSS攻击
密码安全
后端必须使用bcrypt等算法哈希存储
前端不应进行任何密码处理
令牌刷新
生产环境应实现Refresh Token机制
当前方案使用1小时短期令牌
HTTPS
所有认证请求必须通过HTTPS传输
五、完整项目结构jwt-auth-demo/├── backend/│ ├── server.js│ └── package.json└── frontend/ ├── src/ │ ├── api.js │ ├── auth.js │ ├── App.js │ ├── Login.js │ ├── Register.js │ └── Protected.js ├── package.json └── public/六、运行步骤- 启动后端服务:
cd backendnpm installnode server.js- 启动前端应用:
cd frontendnpm installnpm start该实现完整覆盖了用户认证的核心流程,包括JWT的签发、存储、自动附加和验证,并通过React Context实现了全局认证状态管理。生产环境部署时需补充数据库集成和更完善的安全措施。