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

React SPA/CRA+Express电商站点安全与CSRF问题解决方案问询

解决React SPA + Express的API安全与CSRF防护问题

我来帮你一步步梳理问题,先解决API访问权限的顾虑,再重点修复你遇到的CSRF配置错误。

一、API访问权限控制:不是任何人都能随便访问

首先明确:CSRF防护是防止跨站请求伪造,但不能替代身份认证来控制API的访问权限。你需要区分两种API:

1. 公开API(如商品列表、分类)

这类API可以开放给所有用户,但建议通过CORS严格限制来源(你已经设置了origin: "http://localhost:3000",这一步是对的),避免恶意网站直接调用。

2. 私有API(如购物车、订单、用户信息)

必须添加身份认证中间件,只有登录用户才能访问:

  • 如果你用Passport.js,可以在路由里添加passport.authenticate('session')(或对应策略)作为中间件
  • 也可以自己实现JWT验证中间件,要求请求头携带有效token

举个例子,修改你的路由配置:

// 私有API需要先验证用户是否登录
const requireAuth = (req, res, next) => {
  if (req.isAuthenticated()) {
    return next();
  }
  res.status(401).json({ error: "Unauthorized" });
};

app.use('/api/', ensureSession, [
  CategoryRoutes, // 公开API
  ProductRoutes,  // 公开API
  // 私有API添加身份验证
  CartRoutes, 
  AuthRoutes, 
  TrackRoutes,
].map(router => requireAuth(router)));

二、CSRF防护的正确配置:修复"Invalid CSRF token"错误

你的代码里有几个关键错误,导致了token无效或undefined的问题,下面是修正方案:

1. 服务端配置修正(Server.js)

核心问题:

  • 拼写错误+中间件顺序混乱
  • csurf配置错误,没有把token存在客户端可读取的cookie中
  • CORS没有开启credentials: true,导致客户端无法发送cookie

修正后的关键代码:

import express from 'express';
import bodyParser from 'body-parser';
import passport from 'passport';
import connection from './config/conn';
import { /* 你的路由 */ } from './modules';
import session from 'express-session';
import csrf from 'csurf'
import cors from 'cors'
import cookieParser from 'cookie-parser'
import passportSetup from './config/passport-setup';
import keys from './config/keys';
import uuidv4 from 'uuid/v4';
import UAparser from 'ua-parser-js';
import { ensureSession } from './config/sessionCheck';

const app = express();

// 1. 先解析cookie
app.use(cookieParser());

// 2. 配置session
app.use(session({ 
    genid: (req) => uuidv4(),
    name: keys.session.name,
    secret: keys.session.secret,
    resave: false,
    saveUninitialized: false, // 禁止自动创建未初始化的session,更安全
    rolling: true,
    cookie: { 
        secure: process.env.NODE_ENV === 'production', // 生产环境开启HTTPS才设为true
        httpOnly: true,
        maxAge: keys.session.maxAge,
        sameSite: 'strict', // 严格限制跨站cookie
    }
}));

// 3. Passport初始化
app.use(passport.initialize());
app.use(passport.session());

// 4. CORS配置:必须开启credentials,允许客户端发送cookie
app.use(cors({ 
    origin: keys.origin.url, 
    credentials: true 
}));

// 5. CSRF防护配置:把token存在客户端可读取的cookie中
const csrfProtection = csrf({ 
    cookie: {
        key: 'csrf_token', // 指定cookie名称,方便客户端读取
        httpOnly: false, // 允许客户端JS读取这个cookie
        sameSite: 'strict',
        maxAge: 3600000, // 1小时有效期
        secure: process.env.NODE_ENV === 'production'
    }
});
app.use(csrfProtection);

// 6. 安全响应头 + 可选:把token放到响应头,方便客户端读取
app.use((req, res, next)=>{
    res.header('X-XSS-Protection', '1; mode=block');
    res.header('X-Frame-Options','deny');
    res.header('X-Content-Type-Options','nosniff');
    res.header('X-CSRF-Token', req.csrfToken()); // 可选,和cookie二选一即可
    next();
});

// 新增:可选的CSRF token获取接口,方便客户端初始化时获取
app.get('/api/csrf-token', (req, res) => {
    res.json({ csrfToken: req.csrfToken() });
});

// 7. 解析请求体
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 8. 路由配置:私有API记得加身份认证
app.use('/api/', ensureSession, csrfProtection, [ 
    CategoryRoutes, 
    ProductRoutes, 
    CartRoutes, 
    AuthRoutes, 
    TrackRoutes, 
    AddressRoutes, 
    ShippingRoutes, 
    UIRoutes, 
    LookbookRoutes, 
    CheckoutRoutes 
]);

const port = process.env.PORT || 5000;
app.listen(port, (err) => {
    if(err){
        console.log(err);
    }else{
        console.log(`Server running on port ${port}!`);
    }
});

2. 客户端配置修正(Redux + Axios)

核心问题:

  • 你把cookie名称拼错了:服务端设的是csrf_token,客户端写的是csrt_token(少了一个f)
  • 没有全局配置axios自动携带CSRF token,每个请求都要手动写,容易出错

修正后的代码:

首先全局配置axios(建议在项目入口文件如index.js里配置):

import axios from 'axios';
import Cookies from 'js-cookie';

// 配置axios基础路径
axios.defaults.baseURL = 'http://localhost:5000';
// 允许axios发送cookie到服务端
axios.defaults.withCredentials = true;

// 请求拦截器:自动给所有POST/PUT/DELETE请求添加CSRF token
axios.interceptors.request.use(config => {
    const csrfToken = Cookies.get('csrf_token');
    if (csrfToken) {
        config.headers['x-csrf-token'] = csrfToken;
    }
    return config;
}, error => Promise.reject(error));

然后你的Redux action可以简化,不用手动加headers了:

export const fetchCartList = (value) => dispatch => {
    dispatch(loadingCart());
    axios.post('/api/v1/track/cart')
      .then(res => {
          const dataType = typeof res.data.cart_list;
          if (typeof res.data.token_c !== "undefined") {
              localStorage.setItem("hammerstout_c", res.data.token_c);
          }
          if (dataType !== "undefined") {
              dispatch({ type: FETCH_CART_LIST, payload: res.data.cart_list })
          } else {
              dispatch(stopLoadingCart());
              localStorage.removeItem("hammerstout_c");
          }
      }).catch(err => {
          dispatch({ type: GET_ERRORS, payload: err.response.data });
          if (err.response.data.error) {
              dispatch(stopLoadingCart());
          }
      })
}

三、为什么之前会出错?

  1. 拼写错误:客户端读取的cookie名称和服务端设置的不一致,直接导致CSRF is undefined
  2. 中间件顺序错误:csrf中间件必须在cookieParser和session之后,否则无法读取session/cookie来验证token
  3. CORS配置缺失:没有开启credentials: true,客户端不会把cookie发送给服务端,导致服务端无法验证CSRF token
  4. csurf配置错误:一开始设置cookie: false,意味着token存在session里,但客户端无法读取session,所以必须把token存在可读取的cookie中

四、额外注意事项

  • 生产环境必须开启HTTPS,同时把cookie.secure设为true,避免cookie被明文传输
  • 不要把CSRF token存在localStorage里,容易被XSS攻击窃取,用cookie(sameSite: strict)更安全
  • 首次加载时,如果第一个API请求出现token无效,可以先调用/api/csrf-token接口获取token后再发起请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:32:43