正确写法是header()必须在任何输出前调用,框架中优先用中间件或withHeader(),动态允许多域名需校验HTTP_ORIGIN后单个输出;带credentials时Origin不能为*,须配合Access-Control-Allow-Credentials: true;预检请求需显式处理OPTIONS并返回必要头。

PHP中设置Access-Control-Allow-Origin头的正确写法
直接在响应前输出header()是最常用也最易出错的方式。关键不是“能不能加”,而是“加在哪儿”和“加几次”。PHP脚本一旦开始输出(哪怕一个空格),就再也不能用header()发响应头——这时会报Warning: Cannot modify header information。
- 确保
header('Access-Control-Allow-Origin: *')或header('Access-Control-Allow-Origin: https://example.com')出现在任何echo、print、HTML输出之前 - 如果用了框架(如Laravel、ThinkPHP),别在控制器里硬写
header(),优先走中间件或响应对象的withHeader()方法 - 动态允许多个域名时,不能直接写逗号分隔的列表(浏览器不认),得从
$_SERVER['HTTP_ORIGIN']里取值做白名单校验后单个输出
为什么Access-Control-Allow-Origin: * 不适用于带凭证的请求
当前端设置了credentials: 'include'(比如要传cookie或Authorization头),浏览器会强制要求后端把Access-Control-Allow-Origin设为具体域名,而不是*。否则直接拦截,控制台只显示“CORS error”,连网络请求都看不到。
- 必须配合
Access-Control-Allow-Credentials: true使用 -
Access-Control-Allow-Origin值不能是*,只能是匹配的单个源,例如https://app.example.com - PHP里要先判断
$_SERVER['HTTP_ORIGIN']是否在许可列表中,再输出对应值,不能无脑回*
预检请求(OPTIONS)被405或500拦住怎么办
浏览器对PUT、DELETE、带自定义头的POST等请求,会先发一个OPTIONS预检。如果PHP脚本没处理这个方法,Web服务器(如Nginx/Apache)可能直接返回405,或者PHP路由没配导致500。
- 在入口文件或路由层加
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; } - Apache需确认
mod_rewrite没误判OPTIONS为非法方法;Nginx需在location块里显式允许OPTIONS,或用add_header提前返回 - 预检响应里至少要带
Access-Control-Allow-Methods和Access-Control-Allow-Headers,否则浏览器仍会拒接后续请求
PHP内置服务器(php -S)跑API时跨域失效
用php -S localhost:8000 router.php开发时,路由文件router.php负责分发请求,但很多人只在业务逻辑里加header(),忘了预检请求根本没走到业务逻辑,而是被router.php短路了。
立即学习“PHP免费学习笔记(深入)”;
- 在
router.php开头统一加跨域头,而不是分散到每个include的文件里 - 检查
router.php是否对OPTIONS返回了text/html(默认行为),应设Content-Type: text/plain并立即exit - 这种开发服务器不支持
.htaccess或Nginx配置,所有头控制必须由PHP代码完成
实际调试时最容易漏的是:预检通过了,但主请求因为Content-Type: application/json触发了第二次预检,而你只处理了第一次。这时候看Network面板里的两个OPTIONS请求,第二个缺了Access-Control-Allow-Headers: Content-Type,问题就定位到了。











