<p>Access-Control-Allow-Origin 不能为 * 时启用 credentials,须动态校验 Origin 白名单并设置 Allow-Credentials: true;OPTIONS 预检需显式拦截返回 204;注意 BOM、空格导致 header 失效。</p>

Access-Control-Allow-Origin 不能写 * 就完事
只要前端用了 fetch(..., { credentials: 'include' }) 或设置了 withCredentials = true,后端的 Access-Control-Allow-Origin 就绝不能是 *。浏览器会直接丢弃整个响应,连 200 状态都看不到,控制台只报“No 'Access-Control-Allow-Origin' header is present”。
正确做法是白名单校验 $_SERVER['HTTP_ORIGIN'],再动态回写:
- 确认该域名在可信列表里(如
['https://admin.example.com', 'https://app.example.net']) - 用
in_array($_SERVER['HTTP_ORIGIN'], $whitelist)判断,匹配才输出header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']) - 必须同时设置
header('Access-Control-Allow-Credentials: true') - 如果
$_SERVER['HTTP_ORIGIN']为空(比如 curl 直调或某些代理),跳过 Origin 头设置,避免空值污染响应
OPTIONS 预检必须显式拦截并 exit
浏览器对带 Content-Type: application/json、Authorization 头,或用 PUT/DELETE 方法的请求,一定会先发 OPTIONS。PHP 不自动处理,漏掉就卡死——后续请求根本不会发出。
这段逻辑必须放在所有 header() 调用之前,且不能有任何输出:
立即学习“PHP免费学习笔记(深入)”;
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {-
header('HTTP/1.1 204 No Content');(推荐 204,无 body 更稳妥) -
header('Access-Control-Allow-Origin: https://admin.example.com');(与主逻辑一致) header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');exit;
header() 失效的隐蔽原因:BOM、空格、ob_start 干扰
“写了 header 却没生效”是高频问题,PHP 往往静默失败,不报错也不警告。
- 文件开头有 UTF-8 BOM 字节?用编辑器存为“UTF-8 无 BOM”格式
- PHP 标签前有空格、换行、HTML 注释?确保
<?php是文件第一个字符 - 脚本中提前
echo、var_dump、触发了 PHP Notice?加headers_sent($file, $line) && die("Headers sent in $file:$line")快速定位 - 用了
ob_start()但没确认是否已 flush?上线前务必用curl -I http://your-api.com/endpoint直看响应头,别信浏览器 Network 面板缓存
Access-Control-Allow-Headers 必须列全前端实际发的头
预检失败常见原因是漏了某个自定义头。浏览器看到 Authorization 或 X-Api-Key,但 Access-Control-Allow-Headers 没包含它,就会直接拒绝。
检查方式很简单:
- 打开浏览器 DevTools → Network → 点开 OPTIONS 请求 → Headers → Request Headers → 看 “Request Headers” 下有哪些非标准字段
- 把这些字段原样写进
header('Access-Control-Allow-Headers: ...'),用英文逗号+空格分隔 - 常见必含项:
Content-Type、Authorization、X-Requested-With;若前端用了axios.defaults.headers.common['X-Token'] = 'abc',就得加上X-Token - 注意大小写不敏感,但拼写必须完全一致
真正卡住人的不是“怎么配”,而是 header() 被悄无声息吞掉,或者 Origin 和 Credentials 冲突却查不到日志。上线前拿 curl -I 过一遍,比反复刷页面更可靠。



















