为什么跨域问题在面试中高频出现?
在前后端分离架构成为主流的今天,跨域请求处理几乎是每个 PHP 开发者都无法回避的问题。面试官通过这个问题,可以快速判断候选人是否真正理解 HTTP 协议的同源策略、是否具备中间件编程思维,以及能否在框架层面给出优雅的解决方案。
ThinkPHP 作为国内使用最广泛的 PHP 框架之一,其中间件机制为 CORS 处理提供了天然的支持。本文将从同源策略的本质出发,逐步深入到 ThinkPHP 中间件的实现细节,帮助你在面试中从容应对。
同源策略与 CORS 的本质
什么是同源策略
同源策略是浏览器最核心的安全机制之一。所谓“同源”,指的是协议、域名、端口三者完全一致。只要有一个不同,浏览器就会判定为跨域。
需要注意的是,跨域请求并非无法发送,而是响应被浏览器拦截。对于简单请求,请求实际上已经到达服务器并可能产生了副作用;对于非简单请求,浏览器会先发送一个 OPTIONS 预检请求。
CORS 的解决思路
CORS(Cross-Origin Resource Sharing)的核心思路是:由服务器通过特定的响应头告诉浏览器,哪些源可以访问资源。关键响应头包括:
Access-Control-Allow-Origin:允许的源,可以是具体域名或*Access-Control-Allow-Methods:允许的 HTTP 方法Access-Control-Allow-Headers:允许的自定义请求头Access-Control-Allow-Credentials:是否允许携带 CookieAccess-Control-Max-Age:预检请求的缓存时间
这里有一个面试常考的陷阱:当 Access-Control-Allow-Credentials 为 true 时,Access-Control-Allow-Origin 不能设置为 *,必须指定具体域名。很多候选人在这一点上栽跟头。
ThinkPHP 中间件机制回顾
ThinkPHP 5.1 及以上版本引入了中间件(Middleware)机制,它采用洋葱模型处理请求。中间件可以在请求到达控制器之前或响应返回客户端之前执行逻辑,这恰好是处理 CORS 的理想位置。
一个中间件的基本结构如下:
namespace app\middleware;
class Cors
{
public function handle($request, \Closure $next)
{
// 请求前置逻辑
$response = $next($request);
// 响应后置逻辑
return $response;
}
}
注册中间件有三种方式:全局中间件(app/middleware.php)、应用中间件、路由中间件。对于 CORS 处理,通常注册为全局中间件最为合适。
手写一个生产级 CORS 中间件
下面是一个可以直接用于生产环境的 CORS 中间件实现:
namespace app\middleware;
use think\Response;
class Cors
{
protected $allowOrigin = [];
protected $allowMethods = 'GET,POST,PUT,DELETE,PATCH,OPTIONS';
protected $allowHeaders = 'Authorization,Content-Type,X-Requested-With';
protected $maxAge = 86400;
public function handle($request, \Closure $next)
{
$origin = $request->header('origin');
// 预检请求直接返回 204
if ($request->method() === 'OPTIONS') {
return $this->buildResponse($origin, response('', 204));
}
$response = $next($request);
return $this->buildResponse($origin, $response);
}
protected function buildResponse($origin, Response $response)
{
if ($origin && $this->isAllowed($origin)) {
$response->header([
'Access-Control-Allow-Origin' => $origin,
'Access-Control-Allow-Methods' => $this->allowMethods,
'Access-Control-Allow-Headers' => $this->allowHeaders,
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => $this->maxAge,
'Vary' => 'Origin',
]);
}
return $response;
}
protected function isAllowed($origin)
{
return in_array($origin, $this->allowOrigin);
}
}
这段代码有几个值得在面试中强调的设计点:
第一,预检请求的快速返回。 OPTIONS 请求不需要走到控制器,直接在中间件层返回 204,既提升了性能,也避免了路由未定义导致的 404。
第二,动态回写 Origin。 不使用 *,而是将请求的 Origin 回写,这样既能支持携带 Cookie,又能通过白名单控制安全性。
第三,Vary: Origin 响应头。 这是一个容易被忽略的细节。当响应内容因 Origin 不同而不同时,必须设置 Vary: Origin,否则 CDN 或代理服务器可能缓存错误版本的响应。
面试中的高频追问
追问一:预检请求的触发条件是什么?
当请求满足以下任一条件时,浏览器会先发送 OPTIONS 预检请求:使用了 PUT、DELETE、PATCH 等非简单方法;设置了自定义请求头(如 Authorization);Content-Type 为 application/json 等非简单类型。理解这一点,有助于解释为什么前端发送 JSON 请求时总会多出一个 OPTIONS 请求。
追问二:中间件和钩子函数有什么区别?
中间件是框架层面的请求管道机制,遵循洋葱模型,可以同时处理请求和响应;而钩子(Hook)是事件驱动的扩展点,更偏向于在特定时机执行逻辑。CORS 处理涉及请求和响应两个阶段,因此中间件是更合适的选择。
追问三:如何处理多域名动态跨域?
生产环境中往往需要允许多个子域名跨域。可以通过正则匹配实现:
protected function isAllowed($origin)
{
return preg_match('/^https?:\/\/([a-z0-9-]+\.)?example\.com$/', $origin);
}
但要注意,正则匹配必须严谨,避免被 evil-example.com 这类域名绕过。
总结
跨域处理看似简单,实则考察了候选人对 HTTP 协议、浏览器安全模型和框架中间件机制的综合理解。在面试中,建议按照“同源策略原理 → CORS 响应头含义 → ThinkPHP 中间件实现 → 安全细节”这条主线来组织回答,既能展现知识深度,也能体现工程思维。
记住几个关键点:Allow-Credentials 与 * 互斥、预检请求要快速返回、动态 Origin 要配合 Vary 头、白名单校验要防绕过。掌握这些,你就能在这个面试题上给出让面试官眼前一亮的答案。
未经允许不得转载:任鹏个人博客 » ThinkPHP 面试精讲:跨域请求处理与 CORS 中间件

