生产环境CORS报错:Access-Control-Allow-Origin被误判为通配符
我开发了公司网站的登录系统,登录成功后会把refresh token以Cookie形式返回。本地localhost环境运行正常,但生产环境触发CORS报错:
Access to fetch at 'https://my-linux-api.azurewebsites.net/my-site/login' from origin 'https://www.my-site.com' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
我已经在Node.js接口里明确设置Access-Control-Allow-Origin为https://www.my-site.com,也试过用cors包配置,但还是出现上述错误。本地设置http://localhost:3000时没问题。
相关代码
后端路由代码
router.post("/my-site/login", function(req, res) { var email = req.body.Email; var password = req.body.Password; var connection = new Connection(config); var query = "SELECT * FROM dbo.Users WHERE Email = @email" const request = new Request(query, function(err) { connection.close(); if (err) { console.log(err); res.send([]); } } ); request.addParameter('email', TYPES.VarChar, email); var result = []; connection.connect((err) => { if (err) { console.log(err); res.send(err); } else { request.on('row', function(columns) { var col = [] columns.forEach((column) => { col.push(column.value); }); result.push(col); }); request.on('doneProc', function(rowCount) { if (result.length > 0) { if (result[0][4] == password) { if (result[0][8] !== 0) { result[0].splice(4, 1); const accessPayload = ({ User: result[0], Type: "Authentication" }) const accessToken = jwt.sign(accessPayload, process.env.secretKey, { expiresIn: 86400 }); const refreshPayload = ({ User: result[0][0], Type: "Refresh" }) const refreshToken = jwt.sign(refreshPayload, process.env.secretKey, { expiresIn: 172800 }); res.header('Access-Control-Allow-Origin', "https://www.my-site.com"); res.header('Access-Control-Allow-Credentials', true); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); res.setHeader('Authorization', `Bearer ${accessToken}`); res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'Lax', path: "/" }) res.setHeader('Content-Type', 'application/json'); res.json({ success: true, message: "Authorization Successful" }); } else { res.sendStatus(403) } } else { res.sendStatus(401); } } else { res.sendStatus(401) } }) request.on('requestCompleted', (rowCount, more)=> { connection.close(); }) connection.execSql(request); } }) });
CORS配置代码
const cors = require('cors'); const corsOptions = { origin: 'https://www.my-site.com', credentials: true, }; router.post("/my-site/login", cors(corsOptions), function(req, res) { ....
前端请求代码
export const Login = (email: string, password: string, context: any) => new Promise(async (resolve, reject) => { var formData = new FormData(); formData.append('Email', email); formData.append('Password', password); await fetch(url + "my-site/login", { method: 'POST', credentials: 'include', body: formData }) .then((res: any) => { let rawToken = res.headers.get("authorization").split(" ")[1]; var accessToken = jwt.decode(rawToken); context.setUser(accessToken.User); context.setIsAuthenticated(true); localStorage.setItem( "authorization", rawToken ) if (localStorage.getItem("theme") == null) { localStorage.setItem("theme", "light"); } resolve("Success") }) .catch((err) => { console.log(err); if (err.response.status == 403) { reject(403); } else if (err.response.status == 401) { reject(401); } else { reject("Error, please try again later"); } }) });
问题根源分析与解决步骤
1. 清理重复的CORS头配置
你当前同时通过cors包和手动res.header()设置CORS头,这会导致响应头冲突,甚至覆盖正确配置。必须二选一:
- 删除路由代码中所有手动设置的CORS头(
res.header('Access-Control-Allow-Origin', ...)等行) - 优先使用cors包做全局配置,避免仅在单个路由挂载:
// 在app.js/主入口文件中全局挂载cors中间件 const cors = require('cors'); const corsOptions = { origin: 'https://www.my-site.com', credentials: true, }; app.use(cors(corsOptions));
2. 确保OPTIONS预检请求被正确处理
浏览器发送带credentials: 'include'的跨域请求前,会先发送OPTIONS预检请求。如果服务器未正确响应OPTIONS请求,会返回默认的* origin头,触发报错:
- 用cors包全局配置时,它会自动处理OPTIONS请求,无需额外配置
- 如果必须单独给路由配置,需添加OPTIONS方法支持:
router.options("/my-site/login", cors(corsOptions)); router.post("/my-site/login", cors(corsOptions), function(req, res) { ... });
3. 调整Cookie的跨域配置
你的API域名my-linux-api.azurewebsites.net和前端域名www.my-site.com属于完全跨域,原Cookie配置的sameSite: 'Lax'无法满足跨域携带要求:
- 将Cookie的
sameSite设为None,同时保留secure: true(仅HTTPS环境生效,符合生产环境要求):
res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'None', path: "/" })
4. 验证实际响应头
用浏览器开发者工具的Network面板,查看登录请求的响应头:
- 确认
Access-Control-Allow-Origin是精确的https://www.my-site.com,而非* - 确认
Access-Control-Allow-Credentials为true - 检查是否存在重复的CORS头,如有则清理冲突配置
5. 前端额外验证
- 确保
fetch的credentials: 'include'配置正确(当前代码已满足) - 确认后端已配置FormData解析中间件(如
multer),否则req.body会为空,可能引发后续业务错误
内容的提问来源于stack exchange,提问作者Rusty Shackleford

