ThinkPHP 面试精讲:跨域请求处理与 CORS 中间件

为什么跨域问题在面试中高频出现?

在前后端分离架构成为主流的今天,跨域请求处理几乎是每个 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:是否允许携带 Cookie
  • Access-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 中间件

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏