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

Node.js+Express应用前端JWT存储与Authorization头携带方案咨询

前端JWT存储与携带解决方案

问题背景

我基于Node.js、Express、TypeORM和SQL开发公司内部应用,已有User认证实体,登录流程为:

  1. 用户访问www.mysite.com/login
  2. 认证成功后后端生成JWT
  3. 前端跳转至/dashboard
  4. 受保护路由通过中间件校验JWT

现有后端登录路由与令牌生成逻辑(代码如下),用Postman测试时认证中间件工作正常,但浏览器中登录后跳转到/dashboard时,中间件返回"Token not provided",问题出在前端JWT的存储与携带方式上。

async function generateToken(user: any) {
  return jwt.sign(
    {
      id: user.id,
      username: user.username,
    },
    process.env.JWT_SECRET as string,
    { expiresIn: "1h" },
  );
}
async function logUser(username: string, password: string) {
  const userServiceInstance = new userService();
  const users = await userServiceInstance.getUsers();
  const user = users.find(
    (u) => u.username === username && u.password === password,
  );
  if (!user) {
    return null;
  } else {
    const token = await generateToken(user);
    return token;
  }
}
router.post("/login", async (req, res) => {
  const { username, password } = req.body;
  const token = await logUser(username, password);
  if (!token) {
    return res
      .status(401)
      .json({ message: "Invalid credentials", token: null });
  }
  return res.json({ message: "Login successful", token });
});

前端JWT存储方案

1. localStorage(持久化存储)

适合需要记住登录状态的场景,关闭浏览器后token仍保留,内部应用XSS风险较低时优先选用:

// 登录请求成功后存储token
async function handleLogin(username, password) {
  const res = await fetch('/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });
  const data = await res.json();
  if (data.token) {
    localStorage.setItem('authToken', data.token);
    window.location.href = '/dashboard';
  }
}

2. sessionStorage(会话级存储)

仅在当前浏览器会话有效,关闭窗口即清除,适合临时登录场景:

// 登录成功后存储
sessionStorage.setItem('authToken', data.token);

3. Cookie(高安全性)

若后端配合配置安全属性,可避免XSS风险,且请求会自动携带token,适合对安全性要求高的场景:
后端需修改登录接口返回逻辑,设置Cookie:

// 登录接口中替换原res.json逻辑
res.cookie('authToken', token, {
  httpOnly: true, // 禁止JS读取,防XSS
  secure: process.env.NODE_ENV === 'production', // 仅HTTPS传输
  sameSite: 'strict', // 防CSRF
  maxAge: 3600000 // 1小时,与token过期时间一致
});
res.json({ message: "Login successful" });

受保护路由/请求携带JWT的方法

1. API请求手动携带token

封装统一请求函数,自动在请求头添加Authorization: Bearer <token>:

// 封装带认证的请求工具
async function authFetch(url, options = {}) {
  const token = localStorage.getItem('authToken'); // 或sessionStorage.getItem('authToken')
  const requestOptions = {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    }
  };
  const res = await fetch(url, requestOptions);
  // 处理token过期
  if (res.status === 401) {
    localStorage.removeItem('authToken');
    window.location.href = '/login';
    throw new Error('Token已过期,请重新登录');
  }
  return res;
}

// 示例:dashboard页面获取数据
async function loadDashboardData() {
  try {
    const res = await authFetch('/api/dashboard/data');
    const data = await res.json();
    // 渲染数据
  } catch (err) {
    console.error(err);
  }
}

2. 前端路由守卫(框架场景)

如果使用React/Vue等框架,需添加路由守卫,进入受保护路由前校验token:

React Router示例

import { Navigate } from 'react-router-dom';

export function ProtectedRoute({ children }) {
  const hasToken = !!localStorage.getItem('authToken');
  return hasToken ? children : <Navigate to="/login" replace />;
}

// 路由配置
<Route path="/dashboard" element={
  <ProtectedRoute>
    <DashboardPage />
  </ProtectedRoute>
} />

Vue Router示例

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !localStorage.getItem('authToken')) {
    next('/login');
  } else {
    next();
  }
});

// 路由配置
{
  path: '/dashboard',
  component: Dashboard,
  meta: { requiresAuth: true }
}

注意事项

  • 必须处理token过期:后端返回401状态码时,前端清除存储的token并跳转至登录页
  • 若用localStorage/sessionStorage,需注意XSS防护,内部应用可配置Content-Security-Policy减少风险
  • 若用Cookie,后端必须设置HttpOnly、Secure、SameSite等安全属性,避免CSRF与XSS攻击

内容的提问来源于stack exchange,提问作者Vinicius Ferreira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 23:57:27