不使用Express CORS中间件,处理预检请求后POST未发送的问题
直接错误点
你在OPTIONS预检请求的处理代码中,仅设置了状态码但未结束响应。浏览器发送OPTIONS请求后,会等待服务器返回完整的响应内容,若未收到响应结束信号(如res.end()或res.send()),浏览器会一直挂起预检请求,不会触发后续的实际POST请求。
修正后的OPTIONS处理代码:
app.options('/login',(req,res)=>{ console.log(req); res.setHeader('Access-Control-Allow-Origin','*'); res.setHeader('Access-Control-Allow-Credentials',true); res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, X-PINGOTHER,Content-Type, Accept"); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.status(200).end(); // 关键:结束响应 });
额外注意事项:CORS配置冲突
你同时设置了Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true,这两个配置是互斥的——浏览器会直接忽略这种无效组合。如果你的登录请求需要携带凭证(如Cookie),必须将Access-Control-Allow-Origin设为具体的源(即http://localhost:3000),而非通配符*;如果不需要凭证,可直接移除Access-Control-Allow-Credentials配置。
CORS核心原理(无中间件视角)
CORS是浏览器实现的跨域安全机制,分为两种请求类型:
1. 简单请求
满足以下全部条件的请求,浏览器会直接发送实际请求,无需预检:
- 请求方法为
GET/POST/HEAD - 请求头仅包含
Accept/Accept-Language/Content-Language/Content-Type(且Content-Type只能是application/x-www-form-urlencoded/multipart/form-data/text/plain) - 未使用自定义请求头或
ReadableStream等特殊特性
这种情况下,只需在服务器的响应中返回Access-Control-Allow-Origin等必要头即可。
2. 预检请求(Preflight)
当请求不满足简单请求条件时(比如你的登录请求可能携带了Content-Type: application/json),浏览器会先发送一个OPTIONS类型的预检请求,询问服务器:
- 是否允许当前源跨域访问
- 允许的请求方法有哪些
- 允许的请求头有哪些
服务器必须在OPTIONS响应中返回对应的允许配置,并且必须结束响应,浏览器确认预检通过后,才会发送实际的业务请求(如你的POST登录请求)。
无中间件的完整解决方案
推荐全局处理所有OPTIONS请求(避免逐个路由配置),同时在实际业务接口中返回必要的CORS头:
// 全局处理所有OPTIONS预检请求 app.options('*', (req, res) => { // 若需支持凭证,替换为具体源:http://localhost:3000 const allowedOrigin = '*'; res.setHeader('Access-Control-Allow-Origin', allowedOrigin); // 不需要凭证则删除此行 // res.setHeader('Access-Control-Allow-Credentials', true); res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.status(200).end(); // 必须结束响应 }); // 登录接口 app.post('/login',(req,res)=>{ const username = req.body.username; const password = req.body.password; let userexist = userinfo.find(user=>user.username===username); // 与OPTIONS保持一致的Origin配置 const allowedOrigin = '*'; res.setHeader('Access-Control-Allow-Origin', allowedOrigin); // 不需要凭证则删除此行 // res.setHeader('Access-Control-Allow-Credentials', true); if (userexist&&password===userexist.password) { const accessToken = JWT.sign(username, process.env.accTokenSecret); res.status(200).json({'accesstoken':accessToken}); }else{ // 用户名密码错误应返回401(未授权)而非404 res.status(401).json({status:'error',error:'user/password is incorrect'}); } });
内容的提问来源于stack exchange,提问作者user3398356

