浏览器同源策略要求请求的协议、域名和端口完全一致,否则就属于跨域。跨域请求能够正常发出,但浏览器会拦截响应,这时后端需要给响应附加 Access-Control-Allow-Origin 头来明确允许某一个来源,或者使用通配符表示允许所有来源。Apache 本身不会自动输出这个响应头,必须启用 mod_headers 模块,再通过 Header 指令写入。配置可以放在全局 httpd.conf、某个 VirtualHost 或目录级 .htaccess 中,具体选择取决于站点的部署方式。

通常建议优先在 VirtualHost 或 Location 块中集中配置,避免 .htaccess 文件过多导致维护困难。但虚拟主机环境中,修改主配置需要重启 Apache,而 .htaccess 修改后能即时生效,前提是该目录已经开启 AllowOverride。下面从模块准备、基础配置、动态来源与预检处理以及问题排查几个方面展开。
一、确认 mod_headers 模块是否可用
Apache 的 Header 指令由 mod_headers 模块提供。如果模块未加载,启动或重载配置时会提示 Invalid command 'Header'。检查命令通常为 apachectl -M 或者 httpd -M,输出中需要包含 headers_module。在 Debian 或 Ubuntu 上可以通过 a2enmod headers 快速启用,然后执行 systemctl reload apache2。CentOS、AlmaLinux 以及宝塔面板中的 Apache 通常在编译时就包含该模块,但如果精简安装也可能缺失,需要手动在 httpd.conf 中确认。
LoadModule headers_module modules/mod_headers.so
编辑 httpd.conf 后,先使用 apachectl configtest 检查语法,再平滑重启。Windows 环境下配置文件通常位于 C:\Apache24\conf\httpd.conf,模块路径一般写作 modules/mod_headers.so,Apache 对正斜杠与反斜杠都能识别。只要 Header 指令可以被解析,就可以进入响应头的具体配置。
另一个容易忽略的细节是 .htaccess 权限。若把 CORS 配置写入网站根目录的 .htaccess,需要确保站点对应的 <Directory> 块中存在 AllowOverride FileInfo 或 AllowOverride All,否则 Apache 会直接忽略 .htaccess 中的 Header 指令。可以先在 .htaccess 中写入一个明显错误确认是否生效,但更推荐使用 curl 直接查看响应头。
二、基础 CORS 响应头配置
最简单的配置是允许所有来源访问。在 Apache 配置中写入如下内容,所有经过该上下文的响应都会带上 Access-Control-Allow-Origin 头。通配符的含义是浏览器收到响应后不会因来源不同而拦截,适合公开只读接口或静态资源。
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
不过实际业务中更常见的是只允许一个或多个可信前端域名。比如后台管理接口只允许来自 https://www.ipipp.com 的请求,就需要把通配符换成具体来源。这样可以避免无关站点直接在前端脚本中调用接口读取数据。如果接口还需要携带 Cookie 或 Authorization,就必须同时添加 Access-Control-Allow-Credentials 头并设为 true,而且来源不能是通配符。
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "https://www.ipipp.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"
</IfModule>
这里的方法头 Access-Control-Allow-Methods 用来声明浏览器可以在跨域请求中使用哪些 HTTP 方法。GET、POST 通常属于简单方法,但 PUT、DELETE、PATCH 以及带 JSON 内容类型的 POST 会触发预检请求。自定义请求头例如 Authorization、X-Requested-With 也会触发预检。因此仅设置来源头往往不够,前端控制台可能继续报错,提示预检响应中没有允许的方法或头。
如果一段配置需要同时匹配多个域名,可以写多条 Header set,但 Access-Control-Allow-Origin 只能返回一个值。多个来源的经典做法是读取请求头中的 Origin,再与受信任列表比对,而不是写多个重复头。HTTP 规范中重复的 CORS 头可能让浏览器视作无效。
三、动态 Origin 与预检请求处理
受信任来源较多时,推荐结合 SetEnvIfNoCase 和 %{Origin}e 环境变量动态回显 Origin。示例中只允许 www.ipipp.com 和 api.ipipp.com 两个来源,匹配成功才输出对应头。这样既不用写死一个来源,也不会像通配符那样把接口暴露给所有网站。
SetEnvIfNoCase Origin "^https://(www\.)?ipipp\.com$" CORS_ORIGIN=1
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "%{Origin}e" env=CORS_ORIGIN
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization"
Header always set Access-Control-Max-Age "86400"
Header merge Vary Origin
</IfModule>
预检请求使用 OPTIONS 方法,浏览器不会直接发送实际数据,而是先询问服务器是否允许后续请求。Apache 收到 OPTIONS 后可能会交给后端 PHP 处理,也可能由静态文件模块直接返回 200 或 403,因此常见做法是提前拦截 OPTIONS。下面的重写规则可以让 OPTIONS 请求快速返回 204,减少后端压力。
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]
这里要特别注意响应头的持久性。Apache 默认的 Header set 只对成功响应生效,遇到 204、304 或错误响应时可能不会输出。预检如果返回 204,却缺少 Access-Control-Allow-Methods,浏览器判定预检失败,实际请求不会发出。此时应当使用 Header always set,确保无论状态码是否为 2xx 都带上 CORS 头。也可以把 OPTIONS 直接交给一个静态文件处理,并保证该文件所在上下文已配置相关头。
另外建议增加 Vary: Origin。因为动态回显的结果会随 Origin 变化,缺少 Vary 头可能导致代理或浏览器缓存了某个来源的响应,再返回给另一个来源,造成 CORS 判断异常。使用 Header merge Vary Origin 可以在已有 Vary 值后追加,而不是覆盖掉其他值。
四、配置不生效的常见原因与安全建议
配置完成后可以使用 curl 模拟带 Origin 的请求验证。下面命令查看响应头中是否出现预期的 Access-Control-Allow-Origin。应当替换成自己的接口地址。
curl -I -H "Origin: https://www.ipipp.com" https://api.ipipp.com/endpoint
如果响应头没有出现,按顺序排查几个位置。首先是模块状态,apachectl -M 中必须包含 headers_module。其次是配置文件作用域,写在 .htaccess 中时确认 AllowOverride 允许 FileInfo;写在 <VirtualHost> 中时确认请求实际命中的是当前虚拟主机。再有就是 CDN、Nginx 反代或应用框架可能已经覆盖响应头,需要检查链路中是否有其他层丢弃或重写了 CORS 头。
浏览器缓存也是常见干扰。修改配置前可以先无痕窗口或者勾选禁用缓存,否则旧响应头可能仍然生效。对于携带凭证的请求,不要使用通配符来源,也尽量不要把 Access-Control-Allow-Origin 直接写成请求里的 Origin 而不做白名单校验,否则相当于允许所有来源,配合凭证时风险更大。
最后需要明确,CORS 是浏览器侧的读取限制,而不是服务端的身份认证或访问控制。即使不设置允许来源,请求也可能已经到达服务器并执行业务逻辑。因此保护接口安全仍然需要登录态、签名、限流和 CSRF 防护,不能把 CORS 当成安全边界。

