在 PHP 中处理跨域请求,核心是通过设置 CORS(跨域资源共享)响应头,让浏览器允许前端从不同源(协议、域名或端口不同)发起的请求。以下是关键要点和实现方式:
一、基础 CORS 响应头设置
在 PHP 脚本最开头(任何输出前)添加以下代码:
```php
// 允许任意域名访问(仅限开发环境)
header('Access-Control-Allow-Origin: *');
// 允许的 HTTP 方法
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
// 允许的请求头
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
// 处理预检请求(OPTIONS)
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
```
> ⚠️ 注意:`header()` 必须在任何输出(包括空格、BOM、`echo`、`var_dump` 等)之前调用,否则会报 `headers already sent` 错误 。
二、生产环境安全配置
不要使用 `*` 通配符,尤其当请求需携带凭证(如 Cookie、Authorization 头)时:
```php
$allowedOrigins = [
'https://www.your-frontend.com'
];
origin = _SERVER['HTTP_ORIGIN'] ?? '';
if (in_array(origin, allowedOrigins)) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Vary: Origin'); // 避免缓存问题,尤其对 Safari/iOS 重要
}
header('Access-Control-Allow-Credentials: true'); // 允许携带凭证
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
```
> 🔒 关键规则:
> - `Access-Control-Allow-Origin` 不能为 `*`,必须明确指定域名,当同时使用 `Access-Control-Allow-Credentials: true` 时 。
三、处理预检请求(Preflight)
浏览器在发送 非简单请求(如带自定义头、`Content-Type: application/json`、`PUT/DELETE` 方法)前,会先发 `OPTIONS` 预检请求。PHP 必须正确响应:
-
返回 `200 OK`
-
包含必要的 CORS 头
-
不要执行后续业务逻辑,直接 `exit()`
四、其他注意事项
-
编码问题:确保 PHP 文件为 无 BOM 的 UTF-8 编码,避免在 `<?php` 前输出隐藏字节 。
-
框架推荐:如使用 Laravel,可安装官方中间件 [`fruitcake/laravel-cors`](https://github.com/fruitcake/laravel-cors) 统一管理 。
-
Web 服务器层配置:也可在 Nginx 或 Apache 中设置 CORS 头,避免 PHP 代码侵入 :
-
Nginx:使用 `add_header` 指令
-
Apache:在 `.htaccess` 中用 `Header set`
五、常见错误排查
| 问题 | 原因 | 解决方案 |
|------|------|----------|
| `No 'Access-Control-Allow-Origin' header` | `header()` 被输出干扰或未执行 | 检查文件开头是否有空格/BOM,确保在 `session_start()` 等之前调用 |
| 预检失败(405 Method Not Allowed) | 未处理 `OPTIONS` 请求 | 在入口添加 `if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit(); }` |
| 带 Cookie 仍被拒绝 | 使用了 `*` 通配符 | 改为指定具体域名,并设置 `Access-Control-Allow-Credentials: true` |
如需完整示例代码,可参考:[菜鸟教程 - PHP Ajax 跨域问题最佳解决方案](https://www.runoob.com/w3cnote/php-ajax-cross-border.html) 。