返回博客

FastAdmin 新 Controller curl 200 但浏览器报网络错误的真相

在某后台管理系统里新建了一个 PHP 接口模块。后端 curl 测试全部 200,PHP 日志干净,服务器毫无异样。前端却一直弹「网络请求失败」,Chrome Network 面板显示请求已发出但响应被浏览器静默丢弃。

排查了半小时,以为是 nginx 配置问题、以为是前端 axios 拦截器的锅,直到看了 Response Headers——Access-Control-Allow-Origin 根本不存在。

现象

  • 浏览器发出 CORS 预检(OPTIONS),返回头里没有任何 Access-Control-Allow-*
  • 正式请求(POST)服务器返回 200,但浏览器 CORS 校验失败,前端拿不到响应体
  • curl 直接打同一接口,完全正常,数据全回来
  • 同项目其他已有接口在浏览器里运行良好
  • 新接口没有任何认证拦截,无 token 也能通过

根因

FastAdmin 基于 ThinkPHP 5,API 模块有一个专用基类 app\common\controller\Api,它的 _initialize() 会在每次请求进来时做四件事:

protected function _initialize()
{
    check_cors_request();                              // ① 注入 CORS 响应头
    check_ip_allowed();                                // ② IP 白名单
    $this->request->filter('trim,strip_tags,htmlspecialchars'); // ③ 输入过滤
    $this->auth = Auth::instance();                    // ④ Token 鉴权
    // ... 提取 token、校验登录状态 ...
}

check_cors_request() 读取 fastadmin.cors_request_domain 配置,把允许的域写入 Access-Control-Allow-Origin 等响应头。没有这一步,浏览器的同源策略就会无情拦截响应。

而新建 controller 时,手写了:

use think\Controller;
class MyController extends Controller { ... }

think\Controller 是框架的通用基类,没有任何上述初始化逻辑。四件事全部缺席:没有 CORS、没有认证、没有过滤。

坑:curl 成功 ≠ 接口正常

curl 不执行 CORS 策略,所以用 curl 测试永远成功。浏览器则严格执行,响应头里缺 Access-Control-Allow-Origin 就丢弃响应。这让 bug 看起来像「前端问题」,实际是后端忘了继承正确基类。

正解

FastAdmin API 模块下的所有 controller,一律继承 app\common\controller\Api,绝不继承 think\Controller

<?php
namespace app\api\controller;
 
// 错误 ❌
use think\Controller;
class MyController extends Controller { ... }
 
// 正确 ✅
use app\common\controller\Api;
class MyController extends Api
{
    protected $noNeedLogin = [];    // 所有方法均需登录
    protected $noNeedRight = ['*']; // 跳过细粒度权限(登录即可)
 
    public function index()
    {
        // 业务逻辑
    }
}
正解:继承 Api 基类,CORS + 鉴权一步到位

只需把 extends Controller 改成 extends Api_initialize() 自动注入 CORS 头、鉴权逻辑和输入过滤,无需手写任何 CORS 中间件。

附加坑:富文本字段被 strip_tags 吃掉

Api 基类默认开启 strip_tags 过滤,如果接口要接收富文本编辑器的 HTML 内容,所有标签会被剥光。解法是重写 _initialize(),先调用 parent::_initialize() 保留 CORS 和鉴权,再覆盖 filter:

protected function _initialize()
{
    parent::_initialize();              // 保留 CORS + 鉴权
    $this->request->filter('trim');     // 去掉 strip_tags,只保留 trim
}
 
// 然后对富文本字段单独做白名单过滤
if (isset($data['content'])) {
    $data['content'] = strip_tags($data['content'],
        '<p><br><img><strong><em><ul><ol><li><a><h1><h2><h3>');
}
审计存量 controller

用一条命令扫出所有「裸继承」的 controller,有则修,保持 API 目录全部走 Api 基类:

grep -l 'extends Controller' application/api/controller/*.php
# 结果为空则安全

一句话外卖

FastAdmin / ThinkPHP 5 的 API 模块里,extends think\Controller 是一颗无声地雷——CORS 头、鉴权、输入过滤全部缺席,而 curl 测试永远感知不到它的存在。