Enterprise 版本
Licensing — 深度参考
本页是 NextPDF Enterprise 授权接口面的深度参考——该模块负责解析其他每一项 Enterprise 能力。它涵盖权益状态模型(EntitlementEvaluator、EntitlementStatus、EntitlementResult)、强制能力门控(FeatureGate、CapabilityCode、CapabilityRegistry)、双计时器渠道强制机(ChannelEnforcementPolicy、OnlineCheckScheduler),以及签名在线客户端(LicenseClient)。普通的权益评估无需任何网络。在线客户端仅为明确的激活、心跳、停用、在线验证与续订探测操作而存在。内部机制细节保留在源代码仓库的内部文档中,不属于本手册的范围。
可用性与授权
标题为“可用性与授权”的章节此能力随 NextPDF Enterprise(nextpdf/enterprise)发行,并通过一个 Enterprise 层级的授权封套激活。未持有该权益的部署不会加载此能力的类。比较版本并获取授权。
Licensing 是一项基础 Enterprise 接口面。只要 Enterprise 包与 Core 包并列安装,它就始终存在,因为它正是解析其他一切的接口面。NextPDF Pro 没有单独的权益接口面;状态模型、强制机与在线客户端仅随 nextpdf/enterprise 发行。NextPDF Core(Apache-2.0)根本没有权益模型——需要无授权 Apache-2.0 行为的宿主应直接使用 Core 引擎,而非 Enterprise 管线。
公共 API 接口面
标题为“公共 API 接口面”的章节所有类型都位于 NextPDF\Enterprise\Licensing 之下。强制机位于 Channel 子命名空间,运行时组合位于 Runtime,签名状态类型位于 Online。
| 符号 | 参数 | 默认行为 | 返回 | 抛出或失败于 | 备注 |
|---|---|---|---|---|---|
EntitlementEvaluator::evaluate() | ?LicenseKey $license, ?DateTimeImmutable $now = null | 将一份已验证的授权(或 null)映射为完整的权益决策 | EntitlementResult | 无 | null 授权会产出失败即关闭的无授权结果 |
EntitlementEvaluator::evaluateEnforced() | ?LicenseKey $license, EnforcementOutcome $outcome | 将预先计算的强制结果转换为一个权益结果 | EntitlementResult | 无 | 有效版本来自该结果;每一次强制降级都落到 Core |
EntitlementEvaluator::resolveBrandingMode(), ::resolveBrandingStrategy() | ?LicenseKey $license, ?DateTimeImmutable $now = null | 为生成输出解析品牌标识决策 | BrandingMode / BrandingStrategy | 无 | 唯一的品牌标识权威;仅评估渠道会加水印 |
EntitlementResult | 只读值对象 | 携带状态、版本、渠道、品牌标识模式、运行时/更新/支持标志,以及一个可选警告 | — | 无 | 辅助方法:isFullyActive()、isExpired()、isDowngradedToCore()、shouldApplyEvaluationBranding() |
EntitlementStatus | 字符串支撑的枚举 | Active, GracePeriod, DowngradedToCore, EvaluationExpired, NoLicense | — | 无 | PerpetualFallback 已保留但被弃用;仅为使 6.0 之前的消费方仍能编译而保留 |
FeatureGate::hasCapability() | CapabilityCode $capability | 对照强制有效版本与包状态检查该能力 | bool | 无 | 任何解析器错误都失败即关闭到 core |
FeatureGate::requireCapability() | CapabilityCode $capability | 静默通过,或带着结构化的增购上下文抛出 | void | SpectrumAuthenticationException SPEC-LIC-001(未授权)或 SPEC-LIC-002(包已过期) | 该上下文携带 required_capability、required_pack、current_packs、upgrade_info_url |
FeatureGate::isFeatureEnabled(), ::requireFeature() | string $feature | 旧版功能标识符检查 | bool / void | SpectrumAuthenticationException SPEC-AUTH-007(require 路径) | 旧版兼容;优先使用能力检查 |
FeatureGate 访问器 | 无 | 授权自省 | 视情况而定 | 无 | currentEdition(), isLicenseValid(), isInGracePeriod(), getActivePacks(), trialPolicy() |
CapabilityCode | 字符串支撑的枚举 | 规范的能力标识符(core.*、pro.*、enterprise.*、pack.*) | — | 无 | isPack(), packName(), minimumEdition(), forEdition() |
CapabilityRegistry | LicenseKey、路由模式 | 分派前的路由-到-能力解析与包查找 | 视情况而定 | 无 | registerDefaultRoutes() 覆盖已记录的各服务路由 |
LicenseKey | 只读值对象 | 已解析、已验证的授权声明 | — | 无 | isExpired()、isInGracePeriod()、isFullyExpired()、hasFeature()、hasCapability()、activePackNames();maxSlots 仅为合同约定 |
LicenseValidator::buildVerifiedLicenseKey() | array $data | 从一个已验证的载荷构建一个类型化的 LicenseKey | LicenseKey | 违反 schema 时抛出 SpectrumAuthenticationException SPEC-AUTH-001 | 支持 schema 2.0、1.0 及旧版载荷 |
PackDefinition / PackRegistry | 只读值对象 | 带有独立过期与宽限的附加包状态 | 视情况而定 | 无 | isActive(), isFullyExpired(), activePacks(), activeCapabilities() |
TrialPolicy | bool $enabled, int $maxPagesPerSecond = 2, 水印字段 | 试用吞吐量上限策略 | — | 无 | fromLicenseKey(), disabled(), isThrottled();品牌标识权威仍归评估器所有 |
ChannelEnforcementPolicy::enforce() | LicenseKey $license, LocalState $state, int $systemNow, bool $revoked = false | 运行双计时器机,外加吊销与试用重新标注 | EnforcementOutcome | 无 | 吊销会覆盖每一个计时器与试用状态 |
OnlineCheckScheduler::evaluate(), ::recordSuccessfulOnlineCheck() | 授权、本地状态、时间戳 | 双计时器评估;依据已验证的服务器时间续租 | EnforcementOutcome / LocalState | 无 | ONLINE_GRACE_SECONDS 在租约之后授予 48 小时的存活宽限 |
EnforcementDecision / EnforcementReason / EnforcementOutcome | 枚举 + 只读值对象 | Active, Grace, DowngradedToCore 以及精确的原因 | — | 无 | 每个原因都确定性地映射到一个决策 |
Runtime\EntitlementResolver::currentEntitlement() | 无 | 接口:运行时所提供的强制权益 | EntitlementResult | 由实现定义 | EnforcedEntitlementResolver 组合缓存、状态、策略与评估器,失败即关闭到 Core |
LicenseStateCache::get(), ::getOrThrow() | bool $forceRefresh = false | 支撑门控的已验证授权缓存 | ?LicenseKey | getOrThrow():SpectrumAuthenticationException SPEC-AUTH-008;接线无效时抛出 LicenseConfigurationException | invalidate(), isCacheValid() |
LicenseClient::activate(), ::heartbeat(), ::deactivate(), ::verifyOnline() | string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null | 一次签名状态交换;响应会对照固定根进行验证,并绑定到所发送的 nonce | StatusResponse | LicenseClientException(传输、非 200、提供的 nonce 有误);SpectrumAuthenticationException(伪造、篡改、陈旧、nonce 错误、授权错误、类型错误的响应) | 只会发送加盐的指纹哈希,绝不会发送原始指纹 |
LicenseClient::checkRenewal() | string $licenseId, string $customerId | 续订可用性探测;404 是正常的“无可用”答复 | 续订数组(available、download_url、expires_at) | LicenseClientException(不可达、非 200/404,或被策略拒绝的 download_url) | 无签名正文中的 URL 在能逃逸之前会经过 SSRF 筛查 |
LicenseClient::__construct() | 参见签名代码块 | 在构造时验证传输、URL scheme 与固定集 | — | LicenseConfigurationException(不强制策略的传输、不允许的 scheme、无固定值的纯 HTTP、格式错误的固定集) | getPinSet() 为轮换工具暴露活跃的固定集 |
Online\StatusResponse | 只读值对象 | 已验证的签名状态 | — | 无 | 在 revoked / expired / released 时 isLive() 为 false;携带 leaseExpiresAt、席位计数与一份权益快照 |
LicenseChannelMode / OnlineMode / OnlinePolicy | 枚举 + 只读值对象 | 渠道与在线策略声明 | — | 无 | signed_source 对比 ioncube;在线模式 required / optional / disabled,带 intervalDays 与 leaseDays |
LicenseEdition | 字符串支撑的枚举 | Core, Pro, Enterprise | — | 无 | satisfies() 实现层级体系 |
public function __construct(private ?EvaluationBrandingConfig $evaluationBrandingConfig = null)public function evaluate(?LicenseKey $license, ?DateTimeImmutable $now = null): EntitlementResultpublic function evaluateEnforced(?LicenseKey $license, EnforcementOutcome $outcome): EntitlementResultpublic function resolveBrandingMode(?LicenseKey $license, ?DateTimeImmutable $now = null): BrandingModepublic function resolveBrandingStrategy(?LicenseKey $license, ?DateTimeImmutable $now = null): BrandingStrategypublic function __construct(private readonly LicenseStateCache $licenseCache, private readonly EntitlementResolver $entitlementResolver, private readonly string $upgradeInfoUrl = self::DEFAULT_UPGRADE_URL)public function isFeatureEnabled(string $feature): boolpublic function requireFeature(string $feature): voidpublic function currentEdition(): ?stringpublic function isLicenseValid(): boolpublic function isInGracePeriod(): boolpublic function hasCapability(CapabilityCode $capability): boolpublic function requireCapability(CapabilityCode $capability): voidpublic function getActivePacks(): arraypublic function trialPolicy(): TrialPolicypublic function __construct(private readonly ClientInterface $httpClient, private readonly RequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly StatusX5cValidator $statusValidator, private readonly string $baseUrl = self::DEFAULT_BASE_URL, #[SensitiveParameter] private readonly ?string $apiKey = null, array $pinnedPublicKeys = [], array $backupPins = [], private readonly OutboundHttpPolicy $policy = new OutboundHttpPolicy(allowedSchemes: ['https'], connectTimeoutSeconds: 10, readTimeoutSeconds: 30, maxResponseBytes: 10_485_760, followRedirects: false, allowPrivateTargets: false, retryAttempts: 0, retryBaseBackoffMs: 250))public function activate(string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null): StatusResponsepublic function heartbeat(string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null): StatusResponsepublic function deactivate(string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null): StatusResponsepublic function verifyOnline(string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null): StatusResponsepublic function getPinSet(): ?PinSetpublic function checkRenewal(string $licenseId, string $customerId): array行为契约
标题为“行为契约”的章节权益评估会产出五种当前状态之一,每种都带有一个固定的运行时后果:
| 状态 | 运行时 | 有效能力 | 更新 / 支持 | 品牌标识 |
|---|---|---|---|---|
Active | 允许 | 所授权的版本 + 活跃各包 | 是(付费);否(评估) | 无(付费);水印(评估) |
GracePeriod | 允许 | 所授权的版本 + 活跃各包 | 是 | 无(付费) |
DowngradedToCore | 允许 | 仅 Core | 否 | 无(付费);水印(试用) |
EvaluationExpired | 受限 | 无 | 否 | 水印 |
NoLicense | 停用(失败即关闭) | 无 | 否 | 水印 |
- 付费过期绝不会停用已安装的运行时。
runtimeAllowed对每一种付费状态都保持为 true。超过宽限窗口后,有效版本降级到 Core:Core 能力无限期继续运行,所有 Pro/Enterprise/pack 能力门控关闭,更新与支持被撤销。已弃用的EntitlementStatus::PerpetualFallback分支命名了 6.0 之前的过期策略,仅为编译兼容而存在。 - 并非每一种状态都以 Core 执行告终。 宽限期保留完整的所授权版本,并带有一条注明日期的续订警告。评估过期会限制运行时并保留水印。无授权状态会失败即关闭地停用 Enterprise 路径,并对任何旧版输出可见地加水印,因此它绝不会被误认为付费执行。
- 固定的解析顺序。 一份已完全过期的授权解析不出任何能力。包能力延后到该包自身的活跃状态与独立过期。版本能力按严格层级 Enterprise ⊇ Pro ⊇ Core 对照强制有效版本解析。原始的
LicenseKey::$edition绝不会被用于门控咨询。 - 两个计时器;最严格者胜出。 权益计时器(两条渠道)驱动 Active、Grace 以及超过宽限后的 Core 降级。在线存活计时器仅适用于在线模式为
required的ioncube渠道:租约加上 48 小时的存活宽限必须保持存活。超过租约后离线时,premium 会暂停在 Core 层级;稍后一次已验证的在线检查会重新确立租约,premium 随之恢复。signed_source渠道——以及任何非required的在线模式——没有存活计时器,保持离线且具备气隙能力。 - 反回滚是确定性的。 强制会对照一个单调下限进行评估:有效时间取系统时钟与上次已验证时间的最大值,因此时钟回拨不会带来任何额外时间。超过偏移容差与剩余宽限的回滚会强制 Core 降级。
- 租约仅依据可信时间推进。 一次成功的在线检查会记录已验证的签名服务器时间,而绝不是本地时钟,因此本地时钟向前跳跃无法虚增租约。
- 品牌标识只有一个权威。 只有评估渠道会触发水印。付费授权在每一种状态下——包括各降级状态——都解析为无品牌标识。
- 试用是叠加在同一批计时器上的一层策略。 吞吐量被限制(默认每秒 2 页),评估水印生效,且过期严格执行、没有任何宽限。仅所报告的原因会改变;决策与有效版本遵循付费机。
- 在线契约是端到端签名的。 每个请求都携带一个 CSPRNG 客户端 nonce。每个响应都是媒体类型为
application/nextpdf-status+jwt的紧凑 EdDSA JWS,经由 x5c 链验证回一个编译内置的固定根,并绑定到所发送的 nonce、已安装的授权 id 与被调用的端点。旧版无签名封套传输已退役;恰好只剩一条信任路径。所配置的 SPKI 固定值作为X-NextPDF-Pinned-Public-Key头传输,且一个固定集必须包含一个备用固定值。 - 没有运行时席位强制执行。
maxSlots仅为合同约定——普通运行没有任何遥测,也没有强制的回拨上报。
边界情形与失败模式
标题为“边界情形与失败模式”的章节- 对一份已完全过期的授权进行能力检查,会在任何包或版本逻辑运行之前,短路到“不可用”。
- 当基础授权处于活跃状态时,一个附加包可能已过期。此时拒绝会读作包已过期(
SPEC-LIC-002),而非未授权(SPEC-LIC-001),并列出当前活跃的各包。 - 一个存在但不可读或不可解析的本地强制状态文件是一个篡改信号。该机会失败即关闭到 Core 降级,并带
EnforcementReason::CorruptState。一个合法缺失的文件不算损坏,会正常继续。 - 一次低于单调下限、且超过偏移容差(默认 120 秒)与剩余宽限的时钟回拨,会确定性地强制 Core 降级。
- 一个已失效的存活租约会降级一份原本 Active 的
ioncube授权;权益过期则无论租约状态如何都会降级。 evaluate(null)是失败即关闭的:运行时被禁止,水印生效,警告会指明定价 URL。需要无授权 Core 行为的宿主必须直接构造 Core 引擎。- 一份过期的评估授权没有宽限、也没有回退;运行时受限,水印保持。
checkRenewal()将 HTTP 404 视为正常的“无可用”答复。任何其他非 200 都会引发LicenseClientException,而一个违反对外策略的download_url会在能逃出该方法之前失败即关闭。- 一个伪造、篡改、陈旧、nonce 错误或授权错误的状态响应绝不会产出
StatusResponse;验证会抛出SpectrumAuthenticationException。一个已签名但为revoked、expired或released的状态会通过验证,并作为降级信号浮现(isLive()为 false)。 - 一个为不同交换铸造的状态响应会被拒绝:已验证的
response_type必须与被调用的端点匹配。 FeatureGate在任何解析器错误上失败即关闭:有效版本降级到core,且不授予任何 premium 能力。EnforcedEntitlementResolver是端到端失败即关闭的:其链中的任何异常都会产出无授权(Core)结果与一条 PSR-3 错误日志,绝不会授予 premium。
FIPS 模式行为
标题为“FIPS 模式行为”的章节授权层不附加任何自有的密码学策略。授权与状态签名是由宿主密码学提供方执行的 Ed25519 验证。在一个受 FIPS 约束的构建中,提供方不批准的某个算法会在密码学边界处以一个明确错误失败;不会有任何东西静默降级。这些是关于 NextPDF 代码的能力声明:NextPDF 不是一个经验证的密码学模块,不持有任何认证,也不授予任何认证。
符合性
标题为“符合性”的章节| 声明 | 标准 | 条款 |
|---|---|---|
| 授权文件与状态响应是紧凑序列化的 JWS,以失败即关闭方式验证。 | RFC 7515 | §3.1 |
| 授权与状态签名是 Ed25519。 | RFC 8032 | §5.1 |
| 签名响应中的 x5c 链验证回一个编译内置的固定信任锚。 | RFC 5280 | §6.1 |
| 一个已配置的 SPKI 固定集必须包含一个用于尚未部署密钥的备用固定值。 | RFC 7469 | §4.3 |
已验证的声明(iss、aud、exp、nbf、iat)是已注册的声明名。 | RFC 7519 | §4.1 |
所有条款均为转述;不复现任何规范性文本。NextPDF 不作任何认证声明。 支持不等于符合,符合也不等于认证。本模块以能力的形式使其行为与所引条款对齐;对授权或状态封套的接受,由 NextPDF 自身的固定根验证器决定,而非由外部权威决定。
开发说明
标题为“开发说明”的章节- 用一个强制策略的传输(规范做法是
SecurityAwareHttpClient)来构造LicenseClient。原始的 PSR-18 客户端会在构造时被拒绝,从而使授权流量绝不会在缺少反 SSRF、无重定向、字节上限与超时控制的情况下发送。 - 调用方提供的客户端 nonce 必须解码为至少 16 字节的熵。源代码将此下限依据于 RFC 8555 §6.5;该条款不在所引语料之内,因此此处的下限仅依据产品源代码陈述。
- 通过
FeatureGate对功能进行门控;绝不要基于原始的LicenseKey::$edition分支。门控只咨询强制有效版本,并失败即关闭到core。 - 不要在新代码中对已弃用的
EntitlementStatus::PerpetualFallback进行 switch。改为测试isDowngradedToCore()或匹配DowngradedToCore。 - 将一次能力拒绝视为一份补救载荷。把它的结构化上下文与升级 URL 呈现给调用方,而不要将其坍缩为一个通用错误。
- 本模块涉及密码学功能;在你自己的评审中应将其视为安全敏感。容量与席位条款由授权协议管辖,而非由运行时强制执行。本参考不构成法律意见;请咨询你自己的合规与法律顾问。
发布边界
标题为“发布边界”的章节本页仅记录可从外部观察到的行为与受支持的公共 API 接口面。内部命名空间路径、辅助类、机制表、runbook 文件名与工单前缀均不在范围内。
另请参阅
标题为“另请参阅”的章节- Licensing — NextPDF Enterprise — 带有设置与集成指引的能力页面。
- Billing — 深度参考 — 套餐层级、超额策略与结转。
- SaaS — 深度参考 — 路由映射所守护的多租户服务接口面。
- Branding — 深度参考 — 评估器所解析的品牌标识策略。