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

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头触发浏览器的安全校验,最终出现"通配符*与凭证模式冲突"的报错。

修复步骤

  1. 移除后端.htaccess中的CORS配置
    删除<IfModule mod_headers.c>到</IfModule>之间的所有代码,Express的cors中间件已足够处理CORS规则,Apache层的重复配置会引发冲突。

  2. 修正后端.htaccess的OPTIONS请求处理
    将原有的OPTIONS规则替换为:

    # 处理预请求OPTIONS,直接返回200
    RewriteCond %{REQUEST_METHOD} OPTIONS
    RewriteRule ^(.*)$ - [R=200,L]
    

    原规则的重定向逻辑会破坏预请求的正常流程。

  3. 确保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']
    }));
    
  4. 前端请求强制携带凭证
    在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'
          }
      });
      
  5. 检查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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 08:54:51