在 composer.json 的 "scripts" 中配置 "check-license" 调用 composer-licenses 读取 composer.lock 进行许可证白名单校验,避免依赖 unstable 的 composer show --licenses,CI 中应移除 || true 并用 PHP 脚本严格校验以阻断非法许可证。

composer.json 里怎么配 scripts 检查许可证
Composer 本身不校验许可证,但能通过第三方工具(如 license-checker 或 composer-licenses)配合 scripts 实现自动化检查。最轻量、兼容性最好的方式是用 composer-licenses —— 它直接读取 composer.lock,不依赖外部 Node.js 环境。
在 composer.json 的 "scripts" 字段里加一条:
"scripts": {
"check-license": "composer-licenses --format json --no-dev | jq -r '.[] | select(.license | contains("MIT") or contains("Apache-2.0") or contains("BSD-3-Clause")) | .name + " " + .license' || true"
}
注意这里用了 jq 做白名单过滤,|| true 是为了不让非匹配项导致脚本失败(后续再收紧)。
- 必须先全局或项目本地安装
composer-licenses:composer global require mozart/composer-licenses -
--no-dev很关键:避免把phpunit、phpstan这类开发依赖的宽松许可证混进来干扰判断 - 如果没装
jq,换用 PHP 脚本解析 JSON,否则命令会直接报错退出
为什么不能只靠 composer show --licenses
composer show --licenses 输出格式不稳定,且只显示顶层包的 license 字段(常为空),不反映实际安装的依赖许可证。比如 guzzlehttp/psr7 在 composer.json 里没填 license,但它的 composer.lock 里明确记录了 "license": ["MIT"] —— composer-licenses 正是读这个字段。
-
composer show不解析composer.lock,无法得知真实安装版本的元信息 - 某些包的
license是数组(如["BSD-2-Clause", "BSD-3-Clause"]),composer show只输出第一个或干脆不显示 - 它不区分
require和require-dev,没法做生产环境专用检查
CI 中怎么让许可证检查失败时阻断构建
把许可检查做成硬性门禁,关键是让脚本在发现黑名单许可证时真实退出非零状态。不要用 || true,改用过滤后计数判断:
"check-license-prod": "composer-licenses --format json --no-dev | php -r '$j = json_decode(file_get_contents("php://stdin"), true); $bad = array_filter($j, function($p) { return !in_array(strtolower($p["license"][0] ?? ""), ["mit", "apache-2.0", "bsd-3-clause", "bsd-2-clause"]); }); if (!empty($bad)) { echo "Forbidden licenses found:\n"; foreach ($bad as $p) { echo " " . $p["name"] . " (" . implode(", ", $p["license"]) . ")\n"; } exit(1); }'"
- 这段 PHP 内联脚本直接处理 JSON,避免依赖
jq,CI 环境更可控 - 用
$p["license"][0]是因为多数包 license 是单元素数组;少数是字符串(如"MIT"),需额外判空和类型转换,生产环境建议封装成独立 PHP 文件 - CI 配置里调用时别加
set +e或|| true,否则失去阻断意义
常见误报和绕过陷阱
许可证检查不是“查关键字”,而是比对 SPDX 标识符。很多包写的是 "license": "The MIT License (MIT)",这种自由文本会被 composer-licenses 解析为 "MIT" —— 没问题。但以下情况会出问题:
- 包作者写了
"license": "proprietary"或"unlicensed":这类必须人工确认,工具不会自动放行 - 某些 fork 包未更新
composer.json里的 license 字段,但实际代码已换协议:得看composer.lock对应 commit 的 LICENSE 文件,工具无能为力 -
monolog/monolog等老牌包在旧版本中 license 字段为空,但 lock 文件里有值 ——composer-licenses能读到,但需确保你锁的是带 license 信息的版本(通常 v2+ 都有)
真正难处理的永远是“组合授权”(如 LGPL-2.1 WITH Classpath-exception-2.0)和地域性条款,这些没法靠脚本覆盖,得进法务流程。











