本文目录导读:

PHP项目ThinkPHP框架下跨域资源共享(CORS)的终极配置指南:从原理到实战
目录导读
- 为什么你的ThinkPHP接口总被浏览器“拦截”?——CORS基础与痛点
- ThinkPHP 5/6/8跨域配置的三种主流方案(含代码)
- “终极”解决方案:中间件全局处理与预检请求(OPTIONS)
- 实战问答:解决携带Cookie、自定义Header、多域名白名单的疑难杂症
- 安全性与性能优化:如何避免“跨域”变成“跨站攻击”?
- 一套可复用的ThinkPHP跨域配置模板
为什么你的ThinkPHP接口总被浏览器“拦截”?——CORS基础与痛点
在前后端分离的开发模式下,前端运行在 http://localhost:8080,后端接口部署在 http://api.yourdomain.com,当浏览器发起AJAX请求时,同源策略(Same-Origin Policy)会阻止前端读取响应,这并非服务器拒绝了你,而是浏览器主动“拦截”了数据。
核心痛点: 很多PHP开发者直接在控制器里写 header('Access-Control-Allow-Origin: *');,却发现PUT、DELETE请求失败,或者无法携带Session,这是因为CORS分为“简单请求”和“预检请求(Preflight)”,对于PUT、DELETE或自定义Header,浏览器会先发送一个 OPTIONS 请求,如果你没有正确处理这个预检,真正的请求永远不会发出。
ThinkPHP 5/6/8跨域配置的三种主流方案(含代码)
控制器基类中直接添加(最易理解,适合教学)
在 BaseController 的 initialize() 方法中加入:
protected function initialize()
{
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
// 如果是预检请求,直接结束输出
if ($this->request->isOptions()) {
exit();
}
parent::initialize();
}
注意: exit() 至关重要,否则后续路由逻辑会执行。
ThinkPHP 6/8的路由中间件(官方推荐)
创建 app/middleware/CrossDomain.php:
class CrossDomain
{
public function handle($request, \Closure $next)
{
$allowedOrigin = ['https://www.yourdomain.com', 'http://localhost:8080'];
$origin = $request->header('origin');
if (in_array($origin, $allowedOrigin)) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
}
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-Token');
header('Access-Control-Max-Age: 86400'); // 缓存预检结果1天
if ($request->isOptions()) {
return response('')->code(204);
}
return $next($request);
}
}
然后在 middleware.php 注册全局中间件即可。
nginx/Apache层封装(性能最强) 在Nginx配置中添加:
location /api/ {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';
add_header Access-Control-Allow-Headers 'Content-Type, Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
“终极”解决方案:中间件全局处理与预检请求(OPTIONS)
上面方案二其实已经是终极解了,但我必须补充一个关键细节:很多同学反映 Access-Control-Allow-Credentials: true 与 不能同时使用。*如果你需要携带Cookie(Session),则 Origin 必须显式指定为具体域名,不能为 ``**。
完整逻辑如下:
- 从
$_SERVER['HTTP_ORIGIN']获取来源。 - 检查该来源是否在允许的白名单数组内(如:
['https://a.com', 'https://b.com'])。 - 如果命中,动态返回该来源,并设置
Credentials: true。 - 如果未命中,可以不返回CORS头,浏览器自然会拦截。
实战问答:解决疑难杂症
*Q1: 为什么我设置了 Allow-Origin 为 ,但请求还是失败?
因为你的请求可能携带了 withCredentials = true(即携带Cookie),浏览器要求 Origin 必须是具体地址,且 Access-Control-Allow-Credentials 必须为 true。解决办法:* 将 `改为$_SERVER['HTTP_ORIGIN'],并加header('Access-Control-Allow-Credentials: true');`。
Q2: 自定义Header X-Token 总是导致预检失败?
你必须在 Access-Control-Allow-Headers 中明确列出这个Header名,例如前端设置了 X-Token,后端必须包含:header('Access-Control-Allow-Headers: X-Token, Content-Type');。
Q3: 多域名怎么办?
不能使用 ,也不能写死,必须动态获取 Origin 并判断是否在配置数组中,如果不在白名单,直接不输出CORS头即可。
Q4: 为什么OPTIONS请求返回404?
ThinkPHP 6中如果开启了路由强制匹配,OPTIONS 请求可能匹配不到路由。解决: 在 route/app.php 中定义一条兜底路由,或者在中间件的最前端拦截并响应204。
安全性与性能优化:如何避免“跨域”变成“跨站攻击”?
CORS不是万能钥匙,如果你对 Origin 不做任何限制,直接 Allow-Origin: *,相当于任何网站都能请求你的API,容易引发CSRF攻击。
安全策略建议:
- 严格白名单:只允许已知的业务域名。
- 禁止暴露敏感Header:
Access-Control-Expose-Headers不要全开放。 - 结合CSRF Token:对于写操作(POST/PUT/DELETE),建议在Header中携带动态Token。
- 缓存优化:通过
Access-Control-Max-Age减少OPTIONS请求次数,降低服务器压力。
一套可复用的ThinkPHP跨域配置模板
// 中间件核心代码
public function handle($request, \Closure $next)
{
$allow = ['https://www.yourdomain.com'];
$origin = $request->header('origin');
if ($origin && in_array($origin, $allow)) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-CSRF-Token');
header('Access-Control-Max-Age: 86400');
if ($request->isOptions()) {
return response('', 204)->header(['Access-Control-Allow-Credentials' => 'true']);
}
}
return $next($request);
}
记住三点核心:
- 预检请求必须死磕,OPTIONS必须在中间件或入口处被处理并立即返回204。
- 白名单越严格越好,能指定域名绝不使用通配符。
- 调试技巧:使用Chrome DevTools查看
Network面板中的Response Headers,如果看到Access-Control-Allow-Origin存在且符合预期,浏览器就不会拦你。
通过以上配置,你的ThinkPHP项目不仅能完美支持Vue、React等现代前端框架,还能通过谷歌和必应的“移动端可用性”与“安全浏览”SEO评审。注意别在测试环境用Chrome禁用安全策略来摸鱼,正确配置才能应对高并发生产环境。