ThinkPHP 应在控制器中用 jsonp() 或 Response::jsonp() 返回 JSONP 响应,而非在视图中拼接;因视图无法控制 Header、易混入非法字符且不校验 callback,导致前端报错或 XSS。

ThinkPHP 视图层本身不负责输出 JSONP,强行在视图里拼接 callback(...) 是错的路 —— 正确做法是绕过视图,直接用控制器返回 JSONP 响应。
为什么不能在视图里写 JSONP
ThinkPHP 的视图(.html 或 .php 模板)本质是渲染层,它默认会走 Layout、被模板引擎解析、可能被缓存,且无法控制 HTTP 头(如 Content-Type: application/javascript)。更关键的是:JSONP 要求响应体必须是纯 JavaScript 调用语句,而视图输出容易混入空白、BOM、调试信息,导致前端 callback is not defined 或语法错误。
常见错误现象:
- 前端收到 200 响应但控制台报
Uncaught ReferenceError: callback is not defined - 响应内容开头多出空格或换行,破坏 JS 语法
- 使用
json_encode()后手动拼字符串,却忘了对回调函数名做白名单校验,引发 XSS
return jsonp() 是最简可靠的方案
ThinkPHP 5.1+ 内置了 jsonp() 快捷方法,它自动完成三件事:校验 callback 参数、设置正确 header、包裹 JSON 数据。它不经过视图,直接终止流程并输出。
立即学习“PHP免费学习笔记(深入)”;
典型用法:
public function getData()
{
$data = ['code' => 0, 'msg' => 'ok', 'data' => ['id' => 123]];
return jsonp($data);
}
该方法默认从 GET 参数中读取 callback(可配置),并只允许字母、数字、下划线、点号、括号,拒绝恶意输入。如果你的接口约定用 cb 字段传回调名,可以:
- 改用
jsonp($data, ['callback' => 'cb']) - 或全局配置
'default_jsonp_handler' => 'cb'(在app.php中)
自定义 JSONP 响应头和状态码
有些场景需要显式设 HTTP 状态码(比如业务失败时返回 400),或添加额外 header(如 Access-Control-Allow-Origin: *)。这时不能依赖 jsonp() 默认行为,得手动构造:
public function getData()
{
$data = ['error' => 'invalid token'];
$callback = $this->request->param('callback', '', 'htmlspecialchars');
if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $callback)) {
return json(['code' => 400, 'msg' => 'invalid callback name'])->code(400);
}
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
return response("{$callback}({$json})", 200)
->header('Content-Type', 'application/javascript; charset=utf-8')
->header('Access-Control-Allow-Origin', '*');
}
注意点:
- 务必校验
$callback,否则攻击者可注入任意 JS 执行 - 不要用
echo或exit,会绕过 ThinkPHP 的响应生命周期,丢失 header 或日志 -
Access-Control-Allow-Origin: *和 JSONP 是两套机制,前者用于 CORS,后者用于 script 标签跨域;同时加两者不冲突,但 JSONP 场景下后者非必需
JSONP 在 ThinkPHP 6+ 的兼容性提醒
ThinkPHP 6 移除了全局 jsonp() 辅助函数,改用 think\Response::jsonp() 静态方法或 response()->jsonp() 实例方式。控制器内推荐写法是:
use think\Response; // ... return Response::jsonp($data, $callback, 200, ['charset' => 'utf-8']);
另外,TP6 默认关闭了 JSONP 支持(出于安全考虑),需确认 app.php 中未设置 'deny_jsonp' => true;若开启中间件验证,也要确保 JSONP 请求能通过(比如排除 CheckRequestCache 对 script 请求的拦截)。
最容易被忽略的一点:前端发起 JSONP 请求时,callback 参数名必须和服务端约定一致,且服务端校验逻辑要同步更新 —— 否则看似请求发出去了,实际返回的是普通 JSON,前端脚本根本不会执行。



















