跨域的两种解决方法
解决跨域(CORS)问题主要有两类方案:通过服务器配置文件(Apache/Nginx)全局设置和通过 PHP 代码动态设置。两种方案适用场景不同(服务器配置适合全局生效,PHP 适合代码层灵活控制),以下是具体实现:
一、通过 Apache 配置文件解决跨域
适合需要全局生效(所有 PHP 文件 / 接口都支持跨域)的场景,直接在 Apache 的虚拟主机配置或.htaccess
中添加规则。
方式 1:修改 Apache 虚拟主机配置(推荐生产环境)
编辑 Apache 的虚拟主机配置文件(通常位于/etc/httpd/conf.d/域名.conf
或/etc/apache2/sites-available/域名.conf
),添加以下配置:
apache
<VirtualHost *:80>ServerName your-domain.com # 你的域名DocumentRoot /path/to/your/site # 网站根目录(如TP6的public目录)# 跨域核心配置# 1. 匹配允许的源(支持多个域名,用正则匹配)SetEnvIf Origin "http(s)?://(.*\.bjtong\.com|crm\.bjceshi\.com)$" AccessControlAllowOrigin=$0# 2. 设置CORS响应头(always确保所有状态码都带跨域头,包括204、404等)Header always set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOriginHeader always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" # 允许的方法Header always set Access-Control-Allow-Headers "Content-Type, Token, X-Requested-With, Authorization" # 允许的请求头Header always set Access-Control-Allow-Credentials "true" # 允许携带凭证(cookie等)Header always set Access-Control-Max-Age "3600" # 预检请求缓存时间(1小时)# 3. 处理预检请求(OPTIONS方法),直接返回204状态码<IfModule mod_rewrite.c>RewriteEngine OnRewriteCond %{REQUEST_METHOD} OPTIONSRewriteRule ^(.*)$ $1 [R=204,L]</IfModule>
</VirtualHost>
关键说明:
SetEnvIf Origin
:通过正则匹配允许的源(如*.bjtong.com
),避免使用*
(不安全,且不支持带凭证的跨域);Header always set
:always
确保所有响应状态码(包括 204、403、500)都携带跨域头,解决 “部分状态码跨域失败” 问题;- 预检请求处理:
OPTIONS
方法是浏览器发送的跨域预检请求,直接返回 204(无内容)可减少不必要的资源消耗。
方式 2:通过.htaccess 文件配置(适合共享主机)
若没有服务器配置权限,可在网站根目录(如 TP6 的public
目录)创建.htaccess
文件,添加与虚拟主机配置相同的规则:
apache
# public/.htaccess
SetEnvIf Origin "http(s)?://(.*\.bjtong\.com|crm\.bjceshi\.com)$" AccessControlAllowOrigin=$0
Header always set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type, Token, X-Requested-With, Authorization"
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Max-Age "3600"<IfModule mod_rewrite.c>RewriteEngine OnRewriteCond %{REQUEST_METHOD} OPTIONSRewriteRule ^(.*)$ $1 [R=204,L]
</IfModule>
注意:需确保 Apache 启用了mod_headers
和mod_rewrite
模块(可通过apachectl -M
检查,未启用需执行a2enmod headers rewrite
并重启 Apache)。
二、通过 Nginx 配置文件解决跨域
Nginx 的配置逻辑与 Apache 类似,但语法不同,需在server
或location
块中添加跨域规则。
编辑 Nginx 虚拟主机配置
打开 Nginx 的虚拟主机配置文件(通常位于/etc/nginx/conf.d/域名.conf
或/etc/nginx/sites-available/域名.conf
),添加以下配置:
nginx
server {listen 80;server_name your-domain.com; # 你的域名root /path/to/your/site; # 网站根目录# 跨域核心配置location / {# 1. 允许的源(动态匹配请求的Origin,仅允许指定域名)set $allow_origin "";if ($http_origin ~* "^http(s)?://(.*\.bjtong\.com|crm\.bjceshi\.com)$") {set $allow_origin $http_origin;}# 2. 设置CORS响应头(always确保所有状态码生效)add_header Access-Control-Allow-Origin $allow_origin always;add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;add_header Access-Control-Allow-Headers "Content-Type, Token, X-Requested-With, Authorization" always;add_header Access-Control-Allow-Credentials "true" always;add_header Access-Control-Max-Age "3600" always;# 3. 处理预检请求(OPTIONS方法)if ($request_method = OPTIONS) {return 204; # 直接返回204,无需响应体}# 其他配置(如PHP解析)try_files $uri $uri/ /index.php?$query_string;}# PHP解析配置(示例)location ~ \.php$ {fastcgi_pass 127.0.0.1:9000;fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;include fastcgi_params;}
}
关键说明:
set $allow_origin
:通过if
判断请求的Origin
是否在允许列表,动态设置允许的源(避免*
,支持带凭证跨域);add_header ... always
:always
参数确保 Nginx 对所有状态码(包括 404、500)都添加跨域头(Nginx 默认只对 200、204 等成功状态码添加头);OPTIONS
处理:直接返回 204,减少预检请求的处理成本。
配置完成后,执行nginx -t
检查语法,无误后重启 Nginx:systemctl restart nginx
。
三、通过 PHP 代码直接设置跨域(灵活控制)
适合需要动态控制跨域规则(如根据用户角色、请求路径允许不同域名)的场景,直接在 PHP 脚本中通过header()
函数设置响应头。
通用 PHP 跨域处理代码
在 PHP 脚本的最顶部(任何输出前)添加以下代码(可封装为函数或中间件):
<?php
// 1. 定义允许的源(根据实际需求修改)
$allowedOrigins = ['/^https?:\/\/(.*\.bjtong\.com)$/', // 匹配所有bjtong.com子域'/^https?:\/\/(crm\.bjceshi\.com)$/' // 精确匹配crm.bjceshi.com
];// 2. 获取请求的Origin头(浏览器发送的跨域源)
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$allowOrigin = '';// 3. 验证Origin是否在允许列表中
foreach ($allowedOrigins as $pattern) {if (preg_match($pattern, $origin)) {$allowOrigin = $origin;break;}
}// 4. 设置CORS响应头(所有状态码都生效)
if (!empty($allowOrigin)) {header("Access-Control-Allow-Origin: {$allowOrigin}");
}
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Token, X-Requested-With, Authorization");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Max-Age: 3600");// 5. 处理预检请求(OPTIONS方法)
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {http_response_code(204); // 返回204无内容exit; // 终止脚本,避免后续输出
}// 后续业务逻辑(示例)
// echo json_encode(['status' => 'success', 'data' => []]);
?>
如果是设置所有域名可访问,可直接如下简便设置:
<?php
header("Access-Control-Allow-Origin: *"); // 允许所有域名访问
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS"); // 允许的方法
header("Access-Control-Allow-Headers: X-Requested-With, Content-Type, Accept"); // 允许的头部信息
header("Access-Control-Allow-Credentials: true"); // 允许携带凭证信息
框架中使用(以 TP6 为例)
在 TP6 中,推荐将跨域逻辑封装为中间件(全局或路由级生效):
- 创建中间件
app/middleware/Cors.php
,内容同上(将代码放入handle
方法); - 在
config/middleware.php
中注册为全局中间件,或在路由中单独引用。
四、三种方法的对比与选择
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Apache 配置 | 全局生效,无需修改代码,性能好 | 不支持动态规则,需服务器配置权限 | 固定域名、全站点跨域需求 |
Nginx 配置 | 全局生效,性能优于 PHP 代码,支持复杂规则 | 配置语法较严格,需服务器权限 | 高并发场景、固定跨域规则 |
PHP 代码设置 | 支持动态规则(如数据库配置域名) | 需在每个脚本 / 中间件中调用,性能略低 | 动态域名、按路径 / 角色区分跨域需求 |
关键注意事项
- 带凭证跨域:若前端需携带 cookie(
withCredentials: true
),Access-Control-Allow-Origin
不能为*
,必须指定具体域名; - 预检请求:
OPTIONS
方法是浏览器自动发送的,必须处理(返回 204 或 200),否则跨域请求会失败; - 响应头一致性:确保
Access-Control-Allow-Headers
包含前端实际发送的请求头(如自定义的Token
),否则会被浏览器拦截; - HTTPS 兼容:规则中包含
https?
,同时支持 HTTP 和 HTTPS 环境。
根据项目的部署环境和跨域需求,选择合适的方案即可。