Hostinger共享主机前后端分域部署的CORS认证问题求助
- 部署环境:Hostinger共享主机,前后端分域名部署
- 前端:
https://servicios.registroms.com(React + Vite) - 后端:
https://api.registroms.com(Node.js + Express)
- 前端:
- 核心报错:
Access to XMLHttpRequest at 'https://api.registroms.com/auth/adminlogin' from origin 'https://servicios.registroms.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: 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'.
index-BTYFrex6.js:42 POST https://api.registroms.com/auth/adminlogin net::ERR_FAILED - 需求:前端发送带Cookie(用于JWT认证)的POST请求,后端仅允许前端域名访问
后端Express核心代码(index.js)
import cors from 'cors'; import path from 'path'; import { fileURLToPath } from 'url'; import { adminRouter } from './Routes/AdminRoute.js'; import { EmpleadoRouter } from './Routes/EmpleadoRoute.js'; import Jwt from 'jsonwebtoken'; import cookieParser from 'cookie-parser'; import { config } from './config.js'; const app = express(); app.use(cors({ origin: config.CLIENT_URL, methods: ['GET', 'POST', 'PUT', 'DELETE'], credentials: true })); app.use(express.json()); app.use(cookieParser()); app.use(express.static('Public')); const verificarUsuario = (req, res, next) => { const token = req.cookies.token; if (token) { Jwt.verify(token, config.JWT_SECRET_KEY, (err, decoded) => { if (err) return res.status(403).json({ Status: false, Error: "Token erróneo" }); req.id = decoded.id; req.role = decoded.role; next(); }); } else { return res.status(401).json({ Status: false, Error: "No autenticado" }); } }; app.get('/verificar', verificarUsuario, (req, res) => { return res.json({ Status: true, role: req.role, userId: req.id }); }); app.use('/auth', adminRouter); app.use('/empleado', EmpleadoRouter); const PORT = config.PORT || 3001; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
前端.htaccess配置
# Habilitar el motor de reescritura RewriteEngine On RewriteBase / # Configuración del MIME type para archivos JavaScript y JSX AddType application/javascript .js AddType application/javascript .jsx # Asegurar que los módulos ES6 y JSX se sirvan con el tipo correcto <FilesMatch "\.(js|jsx)$"> Header set Content-Type "application/javascript" </FilesMatch> # No redirigir las solicitudes de la API a index.html para /auth y /empleado RewriteCond %{REQUEST_URI} ^/(auth|empleado) [NC] RewriteRule ^ - [L] # Redirigir las solicitudes de la API al backend en api.registroms.com RewriteCond %{REQUEST_URI} ^/(auth|empleado) [NC] RewriteRule ^(.*)$ https://api.registroms.com/$1 [P,L] # Redirigir todas las demás solicitudes a index.html excepto para archivos y directorios existentes RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /index.html [L] # Asegurar que las conexiones WebSocket funcionen correctamente (si las usas) RewriteCond %{HTTP:Upgrade} =websocket [NC] RewriteRule /(.*) wss://api.registroms.com/$1 [P,L]
后端.htaccess配置
# Habilitar el motor de reescritura RewriteEngine On # Asegurar que las solicitudes a la API no redirijan erróneamente a index.js RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # Bloquear el acceso directo a index.js <Files "index.js"> Order allow,deny Deny from all </Files> # Procesar únicamente las rutas que coincidan con auth o empleado RewriteRule ^(auth|empleado)(.*)$ index.js [QSA,L] # Desactivar la exploración de directorios Options -Indexes # Evitar el acceso a archivos sensibles <FilesMatch "\.(env|json|config|lock|log|sh|inc|bak|sql|swp|dist|phar)$"> Order Allow,Deny Deny from all </FilesMatch> # Configuración de CORS para permitir solicitudes desde el frontend <IfModule mod_headers.c> Header set Access-Control-Allow-Origin "https://servicios.registroms.com" Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" Header set Access-Control-Allow-Headers "Content-Type, Authorization" Header set Access-Control-Allow-Credentials "true" # Permitir preflight request (OPTIONS) RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=200,L] </IfModule> # Evitar que el contenido del archivo index.js sea mostrado en consola <Files "index.js"> SetHandler application/x-httpd-php </Files>
问题根源
当前问题是Express的cors中间件与Apache的.htaccess同时配置了CORS规则,导致响应头冲突:预请求(OPTIONS)可能被Apache错误处理,或者重复的CORS头触发浏览器的安全校验,最终出现"通配符*与凭证模式冲突"的报错。
修复步骤
移除后端.htaccess中的CORS配置
删除<IfModule mod_headers.c>到</IfModule>之间的所有代码,Express的cors中间件已足够处理CORS规则,Apache层的重复配置会引发冲突。修正后端.htaccess的OPTIONS请求处理
将原有的OPTIONS规则替换为:# 处理预请求OPTIONS,直接返回200 RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ - [R=200,L]原规则的重定向逻辑会破坏预请求的正常流程。
确保Express的cors配置准确
验证config.CLIENT_URL的值确实为https://servicios.registroms.com(无多余斜杠或拼写错误),也可以直接写死测试:app.use(cors({ origin: "https://servicios.registroms.com", methods: ['GET', 'POST', 'PUT', 'DELETE'], credentials: true, allowedHeaders: ['Content-Type', 'Authorization'] }));前端请求强制携带凭证
在React的请求中必须显式开启凭证携带:- Axios示例:
axios.post('https://api.registroms.com/auth/adminlogin', data, { withCredentials: true }); - Fetch示例:
fetch('https://api.registroms.com/auth/adminlogin', { method: 'POST', credentials: 'include', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } });
- Axios示例:
检查Hostinger Node.js环境配置
确认Hostinger的Node.js应用设置中,启动文件为index.js,且端口使用环境变量process.env.PORT(Hostinger会自动分配端口,无需手动指定3001)。
验证方法
修复后,通过浏览器控制台的Network标签检查:
- OPTIONS预请求的响应头中,
Access-Control-Allow-Origin为https://servicios.registroms.com,Access-Control-Allow-Credentials为true - POST请求无CORS报错,Cookie被正确设置
内容的提问来源于stack exchange,提问作者Ruben

