You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React前端JWT认证:路由保护及Cookie存储Token实现疑问

我来帮你一步步搞定React里的JWT路由保护和Token处理,基于你现有的代码来扩展:

1. 先完善JWT Token的核心处理逻辑

你当前的代码只是用一个布尔值标记认证状态,实际JWT场景下应该存储后端返回的真实Token,还要验证Token的有效性(比如是否过期)。修改你的authService:

import Cookies from 'universal-cookie';
import jwtDecode from 'jwt-decode'; // 需要先安装:npm install jwt-decode

const cookies = new Cookies();
const authService = {
  // 获取存储的JWT Token
  getToken() {
    return cookies.get('authToken');
  },

  // 判断是否已认证:检查Token存在且未过期
  isAuthenticated() {
    const token = this.getToken();
    if (!token) return false;

    try {
      const decoded = jwtDecode(token);
      // JWT的exp字段是过期时间戳(单位秒),转成毫秒和当前时间对比
      return decoded.exp * 1000 > Date.now();
    } catch (err) {
      // Token无效(比如篡改、格式错误),直接清除并返回未认证
      this.signOut(() => {});
      return false;
    }
  },

  // 登录时存储后端返回的JWT Token
  signIn(data, cb) {
    // 假设后端返回的data里包含token字段
    cookies.set('authToken', data.token, { 
      path: '/', 
      expires: new Date(Date.now() + 3600 * 1000) // 和Token过期时间保持一致,比如1小时
    });
    setTimeout(cb, 100);
  },

  // 登出时清除Token
  signOut(cb) {
    cookies.remove('authToken', { path: '/' });
    setTimeout(cb, 100);
  }
};
export default authService;

这里的关键改进:

  • 存储实际的JWT Token而非布尔值,让后端能验证用户身份
  • 用jwt-decode解析Token,检查是否过期,避免使用无效Token
  • 给Cookie设置合理的过期时间,和Token有效期对齐

2. 实现路由保护(React Router v6)

React Router v6推荐用自定义路由组件来保护私有路由,我们创建一个ProtectedRoute组件:

import { Navigate, Outlet } from 'react-router-dom';
import authService from './authService';

const ProtectedRoute = () => {
  const isAuth = authService.isAuthenticated();
  
  // 未认证就跳登录页,已认证则渲染子路由内容
  return isAuth ? <Outlet /> : <Navigate to="/login" replace />;
};

然后在路由配置中使用这个组件包裹所有需要保护的页面:

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
import Login from './Login';
import Dashboard from './Dashboard';
import Profile from './Profile';

function App() {
  return (
    <Router>
      <Routes>
        {/* 公开可访问的路由 */}
        <Route path="/login" element={<Login />} />
        
        {/* 受保护的路由组:只有登录后才能访问 */}
        <Route element={<ProtectedRoute />}>
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile" element={<Profile />} />
          {/* 其他私有页面都放在这个组里 */}
        </Route>
        
        {/* 默认跳转:未登录跳登录,已登录跳仪表盘 */}
        <Route path="/" element={<Navigate to={authService.isAuthenticated() ? "/dashboard" : "/login"} replace />} />
      </Routes>
    </Router>
  );
}

如果用的是React Router v5,写法会略有不同,但v6的这种Outlet方式更简洁灵活,建议升级到v6。

3. 登录/登出组件的实际使用示例

登录组件(Login.jsx)

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import authService from './authService';

const Login = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const navigate = useNavigate();

  const handleLogin = async (e) => {
    e.preventDefault();
    try {
      // 调用后端登录接口获取JWT Token
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password })
      });
      const data = await response.json();
      
      if (response.ok) {
        // 存储Token后跳转到仪表盘
        authService.signIn(data, () => {
          navigate('/dashboard');
        });
      } else {
        alert(data.message || '登录失败,请检查账号密码');
      }
    } catch (err) {
      alert('网络错误,请稍后重试');
    }
  };

  return (
    <div className="login-container">
      <form onSubmit={handleLogin}>
        <input 
          type="email" 
          value={email} 
          onChange={(e) => setEmail(e.target.value)} 
          placeholder="请输入邮箱" 
          required 
        />
        <input 
          type="password" 
          value={password} 
          onChange={(e) => setPassword(e.target.value)} 
          placeholder="请输入密码" 
          required 
        />
        <button type="submit">登录</button>
      </form>
    </div>
  );
};

登出功能示例(比如在Header组件中)

import { useNavigate } from 'react-router-dom';
import authService from './authService';

const Header = () => {
  const navigate = useNavigate();

  const handleSignOut = () => {
    authService.signOut(() => {
      navigate('/login');
    });
  };

  return (
    <header>
      <h2>我的应用</h2>
      <button onClick={handleSignOut}>登出</button>
    </header>
  );
};

4. 生产环境的额外注意事项

  • Token刷新:Token过期前要调用后端的刷新接口获取新Token,避免用户频繁登录。可以在isAuthenticated里检测Token即将过期时自动刷新。
  • Cookie安全性:生产环境一定要给Cookie加这些属性:
    cookies.set('authToken', data.token, {
      path: '/',
      expires: new Date(Date.now() + 3600 * 1000),
      secure: true, // 仅HTTPS下传输
      sameSite: 'strict', // 防止CSRF攻击
      httpOnly: true // 防止XSS攻击(但httpOnly的Cookie前端无法读取,需要后端配合处理)
    });
    
  • API请求携带Token:每次调用需要认证的接口时,要在请求头里带上Token:
    const token = authService.getToken();
    const response = await fetch('/api/protected-data', {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    
  • 全局错误处理:封装一个API请求工具,统一处理401错误(Token过期或无效),自动清除Token并跳转到登录页。

内容的提问来源于stack exchange,提问作者bikky barnwal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:58:25