Bỏ qua để đến nội dung
getnextpdf.com

Enterprise phiên bản

SaaS — Tài liệu tham chiếu chuyên sâu

Module SaaS Enterprise cung cấp các khối xây dựng đa thuê bao cho một dịch vụ dựa trên NextPDF.

  • TenantContext là một value object danh tính bất biến, chỉ được phân giải từ ngữ cảnh đã xác thực.
  • ApiKeyGeneratorApiKeyAuthenticator phát hành và xác thực các API key có prefix, có checksum, lưu dưới dạng hash.
  • QuotaChecker kiểm soát yêu cầu theo hạn mức của từng thuê bao: cảnh báo ở 80%, từ chối ở 100%, và fail-closed khi mức sử dụng chưa xác định.
  • SidecarJwtMinter phát hành các service token HS256 ngắn hạn cho các lệnh gọi liên thành phần.
  • UsageMeterStripeMeteringSyncer kéo các sự kiện sử dụng và đồng bộ chúng tới nhà cung cấp lập hóa đơn với tính idempotency tất định.

Năng lực này đi kèm trong NextPDF Enterprise (nextpdf/enterprise) và được kích hoạt bằng một license envelope bậc Enterprise. Một triển khai không có quyền đó sẽ không nạp các lớp của năng lực này. So sánh các phiên bản và lấy giấy phép.

Bề mặt SaaS là một năng lực Enterprise cơ sở; không tồn tại cờ riêng theo từng tính năng. NextPDF Core (Apache-2.0) và NextPDF Pro không có mô hình đa thuê bao, API key, hay hạn mức nào; năng lực này không có tương đương ở bậc thấp hơn.

Terminal window
composer require nextpdf/enterprise:^3

Tất cả các symbol nằm dưới NextPDF\Enterprise\SaaS.

SymbolTham sốHành vi mặc địnhTrả vềNém hoặc thất bại vớiGhi chú
TenantContextstring $tenantId, string $source, array $scopes = ['read']Value object danh tính bất biếnvalue objectKhôngNguồn: jwt, mtls, api_key; hasScope() / hasAnyScope() kiểm tra scope
TenantContext::singleTenant()không cóThuê bao default cố định với read, write, adminTenantContextKhôngTriển khai đơn thuê bao
ApiKeyAuthenticator::authenticate()string $rawKeyXác thực sáu bước, rồi phân giải contextTenantContextApiKeyAuthenticationException (HTTP 401)source của context là api_key; scope được sao chép từ bản ghi key
ApiKeyAuthenticator::requireScope()TenantContext $context, ApiKeyScope $requiredScopeKhẳng định scope tường minhvoidApiKeyAuthenticationException::insufficientScope() (HTTP 403)Việc thực thi scope là một bước riêng biệt, tường minh
ApiKeyGenerator::generateLive() / ::generateTest()không cóKey mới: prefix, thân base62 32 ký tự (entropy 192-bit), checksum 4 ký tựarray{key, hash, prefix}KhôngPrefix npf_live_ / npf_test_; hash là digest lưu trữ
ApiKeyGenerator::validateChecksum()string $keyKiểm tra hình dạng prefix, độ dài, và CRC32-checksumboolKhôngBộ chặn lỗi gõ nhầm trước mọi tra cứu kho dữ liệu; không phải một biện pháp kiểm soát bảo mật
ApiKeyGenerator::hashKey() (static)string $keyDigest hex SHA-256 của key thôstringKhôngBiểu diễn được lưu trữ duy nhất của một key
ApiKeyGenerator::isLiveKey() / ::isTestKey()string $keyKiểm tra prefixboolKhôngMôi trường hiển thị mà không cần tra cứu
ApiKeyid, tenant, hash của key, prefix hiển thị, scope mask, các thời điểm created/expires/revokedBản ghi key được lưu trữ; văn bản rõ không bao giờ được lưuvalue objectKhôngisActive(), isRevoked(), isExpired(), scopeNames()
ApiKeyScopebacked enum: Read = 1, Write = 2, Admin = 4Mô hình scope kiểu bitmaskenumKhôngmaskFromNames(), fromName(), fullAccess(); các tên không xác định bị bộ dựng mask bỏ qua
ApiKeyRepositoryInterfaceHợp đồng lưu trữ; chỉ lưu hashDo triển khai định nghĩafindByHash(), findActiveByTenant(), store(), revoke()
SidecarJwtMinter::__construct()string $secret, issuer, audience, int $ttlSeconds = 300Từ chối một secret ký dưới 16 byte tại thời điểm khởi tạoinstanceInvalidArgumentExceptionSàn độ mạnh khóa 128-bit; khuyến nghị 32 byte ngẫu nhiên trở lên
SidecarJwtMinter::mint()TenantContext $tenantJWT HS256 với iss, aud, sub, scope, tenant_id, iat, exp, jtistringJsonException khi mã hóa claim thất bạiThời hạn mặc định năm phút; jti là 16 byte ngẫu nhiên, mã hóa hex
QuotaChecker::check()TenantContext $tenant, TenantQuota $quotaĐọc mức sử dụng hiện tại; cảnh báo ở 80%; từ chối ở 100%; từ chối khi mức sử dụng chưa xác địnharray{allowed: bool, warning_percentage: float|null}QuotaExceededException, QuotaUnavailableExceptionCallback cảnh báo được gọi ở cả hai ngưỡng
TenantQuotafloat $maxCuPerPeriod, collections, storage bytes, concurrent jobsGiới hạn theo từng kỳ; hằng số ngưỡng mềm 80%value objectKhôngMặc định của fromConfig(): 10,000 CU, 100 collections, 10 GB, 10 jobs
QuotaExceededException::toErrorEnvelope()không cóError envelope SPEC-QUOTA-001arrayHTTP 402, không thể thử lại; mang theo mức hiện tại, giới hạn, và thời điểm đặt lại
QuotaUnavailableException::toErrorEnvelope()không cóError envelope SPEC-QUOTA-503arrayHTTP 503, có thể thử lại; lý do usage_undeterminable
UsageMeter::pullUsage()array<string, int> $watermarksThăm dò mọi host nguồn sử dụng đã cấu hình từ con trỏ của nóarray{events, instance_id}UsageMeterException khi mọi host đều không thể tiếp cậnChấp nhận gián đoạn một phần; các host không tiếp cận được sẽ được ghi nhật ký và bỏ qua
UsageMeter::getCurrentUsage()string $tenantIdMức sử dụng compute-unit của kỳ hiện tạifloatUsageMeterException khi mức sử dụng không thể xác địnhMột số 0 phân tích được là có thẩm quyền; mức sử dụng chưa xác định sẽ ném lỗi
StripeMeteringSyncer::sync()array<string, int> $watermarksMột chu kỳ kéo, biến đổi, gửiarray{watermarks, sent, failed}Không; các lần gửi thất bại được định tuyến tới callback DLQKéo thất bại trả về một chu kỳ no-op giữ nguyên con trỏ
StripeAdapter::sendMeterEvent()MeterEvent $eventPOST tới nhà cung cấp với một header idempotencyvoidStripeSyncExceptionHTTP 429 và 5xx có thể thử lại; các 4xx khác không thể thử lại
StripeAdapter::sendBatch()list<MeterEvent> $eventsGửi mỗi sự kiện; thu thập các lần thất bạilist<StripeSyncException>KhôngDanh sách rỗng nghĩa là mọi sự kiện đều thành công
MeterEventtên meter, tenant, value, idempotency key, timestampValue object meter-event bất biếnvalue objectKhôngtoStripePayload() tuần tự hóa payload của nhà cung cấp
final readonly class ApiKeyAuthenticator
{
public function __construct(
private ApiKeyRepositoryInterface $repository,
private ApiKeyGenerator $generator,
private LoggerInterface $logger,
) {}
public function authenticate(string $rawKey): TenantContext {}
public function requireScope(TenantContext $context, ApiKeyScope $requiredScope): void {}
}
final class QuotaChecker
{
public function __construct(
private readonly UsageMeterInterface $usageMeter,
private readonly LoggerInterface $logger,
private readonly Closure $quotaAlertCallback,
) {}
/** @return array{allowed: bool, warning_percentage: float|null} */
public function check(TenantContext $tenant, TenantQuota $quota): array {}
}
interface UsageMeterInterface
{
/** @return array<string, mixed> */
public function pullUsage(array $watermarks): array;
public function getCurrentUsage(string $tenantId): float;
}
final class StripeMeteringSyncer
{
public function __construct(
private readonly UsageMeterInterface $usageMeter,
private readonly StripeAdapterInterface $stripeAdapter,
private readonly LoggerInterface $logger,
private readonly Closure $dlqCallback,
) {}
/** @return array{watermarks: array<string, int>, sent: int, failed: int} */
public function sync(array $watermarks): array {}
}
final readonly class SidecarJwtMinter
{
public function __construct(
private string $secret,
private string $issuer = 'nextpdf-enterprise',
private string $audience = 'nextpdf-spectrum',
private int $ttlSeconds = self::DEFAULT_TTL_SECONDS,
) {}
public function mint(TenantContext $tenant): string {}
}
  • Danh tính thuê bao. Một tenant context là bất biến: mã định danh thuê bao, nguồn phân giải, các scope. Danh tính chỉ được phân giải từ ngữ cảnh đã xác thực (jwt, mtls, api_key) — không bao giờ từ một header hay tham số truy vấn do client cung cấp. Một triển khai đơn thuê bao dùng context default cố định với đầy đủ scope.
  • Thứ tự xác thực. Xác thực bằng API key diễn ra theo một thứ tự cố định: checksum, hash SHA-256, tra cứu repository, kiểm tra thu hồi, kiểm tra hết hạn, phân giải context. Các key không xác định, đã thu hồi, và đã hết hạn là ba kết quả riêng biệt, tất cả đều HTTP 401; thiếu scope là HTTP 403.
  • Bí mật của key. Key thô không bao giờ được lưu trữ hay ghi nhật ký; chỉ digest SHA-256 của nó được lưu và tra cứu. Bản thân bộ xác thực không thực hiện phép so sánh bí mật theo từng byte nào; việc tra cứu digest an toàn về thời gian là hợp đồng của phần triển khai repository.
  • Ngưỡng hạn mức. Ở giới hạn mềm 80%, yêu cầu vẫn tiếp tục, tỷ lệ phần trăm cảnh báo được trả về, và callback cảnh báo được kích hoạt. Ở giới hạn cứng 100%, yêu cầu bị từ chối với SPEC-QUOTA-001 (HTTP 402) mang theo thời điểm đặt lại — ngày đầu tiên của tháng kế tiếp, nửa đêm UTC.
  • Hạn mức fail-closed. Mức sử dụng không thể xác định sẽ từ chối yêu cầu với SPEC-QUOTA-503 (HTTP 503, có thể thử lại). Mức sử dụng chưa xác định không bao giờ được coi là 0. Một mức sử dụng 0 thực sự, phân tích được, là có thẩm quyền và cho phép đi qua.
  • Loại trùng cảnh báo. Bộ kiểm tra không loại trùng cảnh báo; việc loại trùng theo từng kỳ là trách nhiệm của callback.
  • Đồng bộ đo lường. Chu kỳ được lên lịch, không bao giờ nằm trên đường yêu cầu. Nó tiếp tục từ các watermark theo từng nguồn và đẩy mỗi con trỏ tiến tới danh tính sự kiện được gửi thành công cao nhất. Idempotency key là tất định — thuê bao, kỳ, danh tính sự kiện — nên một sự kiện được gửi lại sẽ gộp lại nhờ loại trùng phía nhà cung cấp.
  • Kéo thất bại. Một lần kéo thất bại trả về một chu kỳ no-op (sent 0, failed 0) giữ nguyên các watermark; chu kỳ kế tiếp thử lại cùng cửa sổ đó thay vì bỏ qua nó.
  • Service token. Các token là HS256 với một secret dùng chung và mang theo iss, aud, sub, scope, tenant_id, iat, exp, và một jti duy nhất. Thời hạn mặc định là năm phút. Việc khởi tạo từ chối một secret dưới 16 byte, fail-closed.
  • Một key dị dạng thất bại ở checksum và bị từ chối trước bất kỳ truy cập kho dữ liệu nào. Một key đúng định dạng nhưng không xác định bị từ chối sau khi tra cứu. Cả hai đều hiện ra như kết quả key-không-hợp-lệ.
  • Các key không xác định, đã thu hồi, và đã hết hạn dùng các factory ngoại lệ riêng biệt; cờ keyExpired chỉ đúng ở kết quả hết hạn. Hãy ánh xạ chúng tới các phản hồi client riêng biệt.
  • QuotaChecker::check() chỉ trả về khi cho phép đi qua; giá trị allowed được trả về luôn là true. Từ chối và không sẵn dùng là các kết quả ngoại lệ.
  • TenantQuota::usagePercentage() trả về 0.0 cho một hạn mức không dương; fromConfig() thay các giá trị vắng mặt bằng mặc định và kẹp các giới hạn nguyên về ít nhất 1.
  • Các watermark là theo từng nguồn; một watermark thiếu sẽ bắt đầu từ đầu luồng của nguồn đó (con trỏ 0). Một triển khai đa nguồn duy trì các watermark độc lập.
  • Bước biến đổi bỏ qua các sự kiện không phải mảng, các sự kiện thiếu hoặc rỗng ở thao tác hoặc thuê bao, một giá trị không dương, hoặc một thao tác chưa được ánh xạ — mà không làm thất bại chu kỳ. Một sự kiện thiếu một danh tính số nguyên dương dùng được sẽ bị từ chối kèm một cảnh báo: một khóa dự phòng ngẫu nhiên sẽ phá hỏng việc loại trùng phía nhà cung cấp và có thể tính phí trùng cho thuê bao.
  • Mười lần gửi thất bại liên tiếp leo thang thành một mục nhật ký nghiêm trọng; bộ đếm được đặt lại khi có bất kỳ lần gửi thành công nào. Mọi sự kiện thất bại vẫn đến được callback dead-letter.
  • Một body JSON dị dạng từ một host nguồn sử dụng cho ra một danh sách sự kiện rỗng, không phải một lần thất bại chu kỳ. pullUsage() chỉ ném lỗi khi mọi host đã cấu hình đều không thể tiếp cận.
  • Các primitive digest và MAC là SHA-256 và HMAC-SHA256 thông qua nhà cung cấp mã hóa PHP của host. Một bản build bị ràng buộc FIPS sẽ fail closed trên một thuật toán không được chấp thuận thay vì hạ cấp; lớp SaaS không thêm chính sách mã hóa riêng nào.
  • Thân key và mã định danh token đến từ CSPRNG (random_int(), random_bytes()).
  • Checksum CRC32 không phải một biện pháp kiểm soát mã hóa và không bị ảnh hưởng bởi chế độ FIPS.

Các phát biểu dưới đây mô tả năng lực đối chiếu với các điều khoản được trích dẫn. Chúng không phải là tuyên bố chứng nhận; NextPDF không nắm giữ chứng nhận nào cho module này.

Hành viTham chiếu
Ngữ nghĩa not-after của exp trên service tokenRFC 7519 §4.1.4
Tuần tự hóa nén JWS của service tokenRFC 7515 §3.1
Sàn secret HS256 16 byte; không dùng mật khẩu con người nhớ được làm khóa MACRFC 8725 §3.5 (threat: §2.2)
Hợp đồng tra cứu digest an toàn về thời gian của repositoryOWASP ASVS 5.0 §11.2.4
Digest lưu trữ API key SHA-256FIPS 180-4 (code-declared)

Các trích dẫn RFC 8725 và OWASP ASVS 5.0 được xác minh bằng RAG; các mã định danh tham chiếu đầy đủ được ghi trong frontmatter của trang này. Các tham chiếu FIPS 180-4, FIPS 198-1, và BSI TR-02102-1 được khai báo trong mã của mã nguồn sản phẩm (hash('sha256', …) và sàn khóa được ghi trong tài liệu của bộ minter); chúng không được truy xuất từ kho RAG cho trang này. Yêu cầu an toàn về thời gian của ASVS §11.2.4 ràng buộc phần triển khai repository do người vận hành cung cấp, chứ không phải bản thân lớp authenticator.

  • Hãy cung cấp các phần triển khai bền vững cho ApiKeyRepositoryInterfaceStripeAdapterInterface; gói cung cấp các hợp đồng và một client nhà cung cấp PSR-18, chứ không phải phần lưu trữ.
  • Các phụ thuộc chỉ là các abstraction PSR: PSR-3 logger, PSR-18 HTTP client, các factory request và stream PSR-17. Không cần SDK của nhà cung cấp nào.
  • Hãy chạy đồng bộ đo lường như một job theo lịch. Lưu bền vững các watermark được trả về sau mỗi chu kỳ.
  • Hãy đưa tỷ lệ phần trăm cảnh báo hạn mức ra cho client, ví dụ như một header cảnh báo, và loại trùng các cảnh báo hạn mức theo từng kỳ trong callback.
  • Hãy cấp secret cho bộ minter token từ cấu hình dưới dạng một giá trị ngẫu nhiên entropy cao; khuyến nghị 32 byte ngẫu nhiên trở lên. Không bao giờ suy dẫn nó từ một mật khẩu.
  • Prefix của key làm cho môi trường hiển thị mà không cần tra cứu; các key sandbox và production không bao giờ đụng nhau vì prefix tham gia vào digest được lưu trữ.
  • Chi tiết cơ chế nội bộ nằm trong tài liệu nội bộ của repository nguồn và nằm ngoài phạm vi của tài liệu hướng dẫn này.

Trang này chỉ ghi lại hành vi có thể quan sát được từ bên ngoài và bề mặt API công khai được hỗ trợ. Các đường dẫn namespace nội bộ, các lớp helper, các bảng cơ chế, các tên tệp runbook, và các tiền tố ticket đều nằm ngoài phạm vi.