开源许可证选型是每个技术团队都躲不过的工程问题:选错了,产品可能被迫开源核心代码;查漏了,一个 GPL 依赖就能让整套商业代码陷入合规风险。本文不谈法条堆砌,只讲工程师视角的实操:MIT、Apache 2.0、GPL 三类许可证的义务边界在哪,什么场景算「分发」,怎么用三行命令扫清依赖家底,以及如何在 CI 里自动拦住高风险许可证。
先说结论:绝大多数团队的合规风险不来自「选哪个许可证」,而来自「不知道自己用了什么」。所以本文后半段的扫描与卡点,比前半段的条款对比更值钱。
一、三大阵营:义务强度决定合规成本
抛开上百种许可证的细节,工程上只需按「著佐权(copyleft)传染强度」分三档记忆。传染强度越高,你把它塞进闭源产品时要付的代价越大。
| 阵营 | 代表许可证 | 核心义务 | 闭源商用 |
|---|---|---|---|
| 宽松型 | MIT、BSD-2/3、ISC | 保留版权声明与许可证全文 | 可以,几乎无成本 |
| 宽松+专利型 | Apache-2.0 | 保留 LICENSE 与 NOTICE、标注修改、显式专利授权 | 可以,需保留 NOTICE |
| 弱著佐权 | LGPL、MPL-2.0、EPL-2.0 | 被修改的库/文件本身需以同许可证开源 | 可以,需隔离边界 |
| 强著佐权 | GPL-2.0/3.0 | 分发衍生作品时整体须以 GPL 开源 | 高风险 |
| 网络著佐权 | AGPL-3.0 | 通过网络提供服务也视同分发,须开源 | SaaS 场景极高风险 |
| 源码可见(非开源) | SSPL、BUSL-1.1、RSALv2 | 限制以其提供竞品托管服务 | 视条款,通常禁止 |
最后一行值得单独提醒:SSPL、BUSL 这类协议并未获 OSI 认定为开源许可证,属于「源码可见」。近两年 MongoDB、Terraform、Redis 陆续改用此类协议,社区随即出现 OpenTofu、Valkey 等分叉。这意味着你依赖的组件许可证不是一成不变的——升级一个大版本,许可证可能已经换了。
二、MIT 与 BSD:几乎零义务,但别漏署名
MIT 是最省心的选择:允许商用、修改、闭源分发、再授权,唯一义务是在软件副本或实质部分中保留版权声明和许可证文本。BSD-3-Clause 在 MIT 基础上多一条「不得用原作者名义为衍生产品背书」。
工程上最常踩的坑是:前端项目把上百个 MIT 依赖打包成一个压缩后的 bundle,却没有随包附带任何许可证声明。严格说这已违反署名义务。正确做法是在构建流程里生成一份聚合许可证文件(webpack 的 license-webpack-plugin、Vite 的 rollup-plugin-license 都能自动产出),随产物一起发布。
三、Apache 2.0:真正的价值在专利条款
Apache-2.0 常被误当成「啰嗦版 MIT」,但它多出的两点对企业极其重要。
- 显式专利授权:贡献者授予你使用其相关专利的权利,并附带「专利反击终止」条款——谁对项目发起专利诉讼,其授权自动终止。这是大厂偏好 Apache-2.0 的主因。
- NOTICE 与修改标注:若上游带 NOTICE 文件,你分发时必须一并保留;对源码所做的显著修改需在文件中标注。
另有一个历史兼容性细节要记牢:Apache-2.0 与 GPL-2.0 被 FSF 认定为不兼容,与 GPL-3.0 才兼容。所以把 Apache-2.0 代码合进一个 GPL-2.0-only 项目是有问题的,反向亦然。这类判断建议在技术方案评审阶段就写进设计文档,而不是等发版前才发现。
四、GPL 与 AGPL:搞清「分发」这条线
GPL 的传染性触发条件是分发(distribution),不是「使用」。这条线画在哪,直接决定风险大小:
- 公司内部自用、不对外交付二进制:GPL-2.0/3.0 通常不触发开源义务。
- 交付客户私有化部署包、发布 App、卖硬件固件:属于分发,衍生作品须以 GPL 开源并提供完整源码。
- 只提供 SaaS 服务、不给二进制:GPL 一般不触发,但 AGPL-3.0 第 13 条把「通过网络与用户交互」等同于分发——你的 SaaS 后端一旦含 AGPL 组件的衍生代码,就得向使用者提供对应源码。
调用方式不改变许可证性质
常见的自我安慰是「我只是通过命令行调用它,没有链接进来」。判断是否构成衍生作品,关键看结合的紧密程度:静态链接、动态链接、同进程加载通常被视为衍生;通过独立进程 + 标准协议(HTTP、管道)通信,业界普遍认为耦合更松。但这是灰区而非豁免通道,涉及核心商业代码时应让法务定夺。本文是工程实践视角,不构成法律意见。
五、LGPL 与 MPL:可用的中间地带
LGPL 允许闭源程序动态链接其库,条件是用户能替换该库(保留重新链接的可能);你改了库本身,改动部分要以 LGPL 回馈。MPL-2.0 更适合工程落地:传染粒度是文件级——你改了哪个 .go/.rs 文件,只需开源那些文件,新增的独立文件可闭源。
如果你要开源一个希望被商业产品广泛集成、又不想被完全白拿的组件库,MPL-2.0 往往比 GPL 和 MIT 都更贴合诉求。
六、五个高频误区
| 说法 | 真相 |
|---|---|
| 「只要不改代码就没有义务」 | 错。分发即触发署名义务,MIT 也要求随附许可证。 |
| 「代码放 GitHub 上就是开源可商用」 | 错。没有 LICENSE 文件默认保留全部权利,法律上你无权商用。 |
| 「GPL 依赖只在测试环境用没关系」 | 基本成立,但要确保它不被打进发布产物;devDependencies 需与运行时依赖分开扫描。 |
| 「许可证选了就不用管了」 | 错。上游可能变更协议(如 Redis、Terraform),需定期复查。 |
| 「AI 生成的代码没有许可证问题」 | 存疑。若模型输出与 GPL 训练语料高度雷同,风险仍在,关键模块建议人工复核。 |
七、给自己的项目选许可证:三步定稿
决策其实很简单:想要最大采纳率选 MIT;有专利资产或企业协作选 Apache-2.0;想防白拿选 MPL-2.0;想强制生态回馈且不做闭源商业版选 GPL/AGPL(这也是很多公司「开源版 AGPL + 商业授权」双许可策略的基础)。
定稿后落到仓库里的动作是三件事:根目录放 LICENSE 全文、源文件头加 SPDX 标识、包元数据声明许可证字段。
# 1. 拉取标准许可证全文(避免手抄漏字)
curl -sL https://raw.githubusercontent.com/licenses/license-templates/master/templates/mit.txt -o LICENSE
# 2. 源文件头统一加 SPDX 标识(机器可识别,扫描工具依赖它)
# Java / Go / Rust / C 风格:
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 fsdata.site
# Python / Shell 风格:
# SPDX-License-Identifier: MIT
# 3. 包元数据声明(三种生态示例)
# package.json
"license": "MIT"
# pyproject.toml
license = { text = "MIT" }
# Cargo.toml
license = "Apache-2.0 OR MIT"
SPDX 标识不是形式主义:后面所有自动化扫描都靠它和包元数据识别,缺了就只能靠人工比对文本,成本差一个数量级。
八、扫清依赖家底:一条命令查一个生态
合规工作的 80% 是「知道自己用了什么」。各语言都有成熟工具,先跑一遍摸清现状,把结果存档作为基线。
# Node.js:汇总所有依赖许可证分布
npx license-checker --summary --production
# 只允许白名单,出现其他许可证直接非零退出
npx license-checker --production --onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC"
# Python:导出为 Markdown 便于评审归档
pip install pip-licenses
pip-licenses --format=markdown --with-urls --order=license > licenses.md
# Rust:cargo-deny 用配置文件管控(deny.toml 中 allow/deny 列表)
cargo install cargo-deny
cargo deny check licenses
# Java / Maven:聚合第三方许可证清单
mvn license:aggregate-add-third-party
# Go:
go install github.com/google/go-licenses@latest
go-licenses report ./... > licenses.csv
# 跨语言通用:生成 SBOM 后统一分析(容器镜像同样适用)
syft dir:. -o spdx-json=sbom.json
syft packages docker:myapp:latest -o table | grep -iE "gpl|sspl|busl"
第一次扫描出现几十条 GPL/LGPL 命中是常态,别慌。按「是否进入发布产物」分级处理:仅构建期或测试期使用的(编译器、lint 工具、测试框架)风险低;被打进二进制或镜像的才需要逐个评估替换。这份清单本质上就是一笔技术债务,适合登记后按季度偿还,而不是一次性停工整改。
九、在 CI 里设卡点:让风险进不来
人工扫描只能解决存量,增量必须靠自动化。最有效的位置是 PR 检查——新引入的高风险依赖当场被拦住,成本远低于上线前救火。下面这段可直接放进已有的GitHub Actions 流水线。
name: license-check
on:
pull_request:
paths: ["package.json", "package-lock.json", "pyproject.toml"]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci --omit=dev
# 白名单卡点:出现名单外许可证则 exit 1,PR 变红
- name: Enforce license allowlist
run: |
npx license-checker --production \
--onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;0BSD;Unlicense;CC0-1.0" \
--excludePrivatePackages
# 显式黑名单二次兜底,避免白名单写漏
- name: Block copyleft
run: |
npx license-checker --production --json > lic.json
if grep -qiE '"(A?GPL-[23]|SSPL|BUSL)' lic.json; then
echo "::error::检测到强著佐权或源码可见许可证,请评审后再合并"
exit 1
fi
- uses: actions/upload-artifact@v4
if: always()
with:
name: license-report
path: lic.json
两点实战经验:一是白名单要配黑名单兜底,因为部分包的 license 字段写法不规范(如 (MIT OR Apache-2.0)),只靠白名单容易误判放行;二是别一上线就设成强制失败,先跑两周只告警的观察期,把历史遗留清干净再开启阻断,否则团队会立刻学会加 skip。同样的思路也适用于pre-commit 钩子——本地就发现问题,比 CI 里发现更省时间。
十、团队落地清单
| 动作 | 频率 | 产出 |
|---|---|---|
| 全量依赖扫描建立基线 | 一次性 | licenses.md / sbom.json 存档 |
| CI 白名单 + 黑名单卡点 | 每个 PR | 阻断新增高风险依赖 |
| 发布产物附聚合许可证文件 | 每次发版 | THIRD-PARTY-NOTICES.txt |
| 复查上游协议变更 | 每季度 | 变更影响评估报告 |
| 关键组件双许可/商业授权确认 | 接入前 | 法务确认记录 |
把这五件事挂到既有流程上即可,不需要新增专职角色:扫描挂 CI,聚合声明挂发布脚本,季度复查挂进版本规划会。若团队还在梳理协作规范,可以顺带把它并入分支模型与发布流程一起定稿。
小结
开源许可证的工程要点可压缩成四句话:宽松型(MIT/Apache)随便用但要署名;Apache-2.0 的专利条款是企业刚需;GPL 看「分发」、AGPL 连 SaaS 都算分发;SSPL/BUSL 根本不是开源,商用前必读条款。而真正降低风险的动作只有两个——先扫清家底建立基线,再用 CI 卡住增量。
如果你打算从使用者转为贡献者,向上游提交代码时也会遇到 CLA/DCO 签署等许可证相关流程,可以继续阅读开源项目贡献指南。




