getmilpa/live-web
GitHub: getmilpa/live-web
为 Milpa Live 组件生命周期提供 HTML 渲染器和安全加固的 HTTP 实时交互端点的 PHP Web 框架。
Stars: 0 | Forks: 0
# Milpa Live Web
[](https://github.com/getmilpa/live-web/actions/workflows/ci.yml)
[](https://packagist.org/packages/milpa/live-web)
[](https://www.php.net/)
[](LICENSE)
[](https://getmilpa.github.io/live-web/)
`milpa/live` 定义了与渲染目标无关的组件生命周期(挂载 / 处理 / 渲染、契约、数据源)。`milpa/live-web` 则使该生命周期可以通过 HTTP 访问,并能在浏览器中渲染:它包含用于表单、看板和自动补全组件系列的 HTML 渲染器,一个零依赖的 XHTML 状态/交互传输编解码器,以及安全组件包 —— 包含 HMAC 状态签名、CSRF 和单次使用 nonce/重放保护 —— `LiveEndpoint` 将这些组合成一个坚固的 HTTP 入口点。
## 安装
```
composer require milpa/live-web
```
## 它是什么
- **`LiveEndpoint`** —— HTTP 实时循环终端。验证请求方法、CSRF token 和已签名的状态信封;根据组件自身的契约对请求的操作进行授权;分发至组件的 `handle()`;并返回全新渲染的 HTML 以及一个新的已签名状态信封供客户端保留。每一个常规失败(错误的方法、缺少字段、无效签名、重放、未授权的操作)都会返回类型化的错误响应 —— 它绝不会因为预期的错误输入而抛出异常。
- **HTML 渲染器** —— `AutocompleteHtmlRenderer`、`FormPrimitiveHtmlRenderer`(`input`、`textarea`、`select`、`checkbox`)和 `DashboardHtmlRenderer`(外壳、侧边栏、顶栏、网格、面板、指标卡片、数据表等)各自通过 `@milpa/design` 系统将组件的状态转换为绑定 Alpine 的 HTML。`XhtmlComponentCompiler` 允许你直接在标记中编写 `` 组件树,并通过相同的渲染器流水线对其进行编译。
- **传输** —— `XhtmlStateTransferCodec` 将组件的状态/交互编码为带有 base64 JSON payload 的单一 `` / `` XHTML 元素。它本身不携带任何安全保证 —— 它是 `$inner` 编解码器,由 `SignedXhtmlStateTransferCodec` 进行包装。
- **安全** —— `HmacStateSigner`、`HmacCsrfGuard`、`FileNonceStore`、`SignedXhtmlStateTransferCodec`、`ContractInteractionAuthorizer` 和 `AllowListCorsPolicy` 是 `LiveEndpoint` 旨在信任的、具体的生产级类 —— 请参阅下方的[安全](#security)。
- **`AlpineRuntimeAdapter`** —— 内置渲染器所目标的 `Milpa\Live\Contracts\Client\ClientRuntimeAdapterInterface` 实现;它使用 `data-milpa-*` 属性标记根元素,并描述 Alpine runtime 脚本所使用的启动 payload/asset。
## 快速示例
连接真正的安全类并通过 `LiveEndpoint` 发起一次交互 —— 这是 `tests/Http/LiveEndpointTest.php` 中执行测试的精简版本,可直接针对此包自身的 `vendor/` 运行:
```
use Milpa\Live\Adapters\Alpine\AlpineRuntimeAdapter;
use Milpa\Live\Components\Autocomplete\AutocompleteComponent;
use Milpa\Live\DataSource\ArrayDataSource;
use Milpa\Live\DataSource\InMemoryDataSourceRegistry;
use Milpa\Live\Http\LiveEndpoint;
use Milpa\Live\Http\LiveHttpRequest;
use Milpa\Live\Rendering\AutocompleteHtmlRenderer;
use Milpa\Live\Runtime\InMemoryComponentRegistry;
use Milpa\Live\Security\ContractInteractionAuthorizer;
use Milpa\Live\Security\FileNonceStore;
use Milpa\Live\Security\HmacCsrfGuard;
use Milpa\Live\Security\HmacStateSigner;
use Milpa\Live\Security\SignedXhtmlStateTransferCodec;
use Milpa\Live\Transport\XhtmlStateTransferCodec;
use Milpa\Live\ValueObjects\ComponentContext;
// A real component (milpa/live) over a real data source.
$sources = new InMemoryDataSourceRegistry();
$sources->register(new ArrayDataSource('customers.search', [
['value' => 'acme', 'label' => 'Acme Studio', 'search' => 'agency design'],
['value' => 'milpa', 'label' => 'Milpa Labs', 'search' => 'framework components'],
]));
$components = new InMemoryComponentRegistry();
$components->register('autocomplete', new AutocompleteComponent($sources));
// The real security wiring: HMAC-signed state + single-use replay nonce + CSRF.
$codec = new SignedXhtmlStateTransferCodec(
new XhtmlStateTransferCodec(),
new HmacStateSigner($_ENV['LIVE_STATE_SECRET']),
new FileNonceStore(sys_get_temp_dir() . '/milpa-live-nonces.json'),
);
$csrf = new HmacCsrfGuard($_ENV['LIVE_CSRF_SECRET']);
$endpoint = new LiveEndpoint(
components: $components,
codec: $codec,
authorizer: new ContractInteractionAuthorizer($components),
csrf: $csrf,
route: '/live/autocomplete',
renderers: ['autocomplete' => new AutocompleteHtmlRenderer(new AlpineRuntimeAdapter(), $codec)],
);
// Mount the initial state (server-rendered on the page) and issue a CSRF token for the session.
$context = new ComponentContext('customer-picker', route: '/autocomplete-demo');
$state = $components->get('autocomplete')->mount(['name' => 'customer', 'source' => 'customers.search'], $context);
$sessionId = 'demo-session'; // however your app tracks sessions (e.g. the PHP session id)
$csrfToken = $csrf->issueToken($sessionId, '/live/autocomplete');
$envelope = $codec->encodeState($state); // embed both in the SSR'd page
// The client echoes $envelope + $csrfToken back on every interaction.
$response = $endpoint->handle(new LiveHttpRequest(
method: 'POST',
action: 'search',
stateEnvelope: $envelope,
payload: ['query' => 'mil'],
sessionId: $sessionId,
csrfToken: $csrfToken,
));
$response->status; // 200
$response->body['data']; // ['items' => [['value' => 'milpa', ...]]]
$response->body['html']; // freshly rendered …
$response->body['state']; // a freshly signed envelope for the next round-trip
```
## 安全
`LiveEndpoint` 的信任模型,用一句话来概括:**客户端无法持有签名密钥,因此它从不自己构建状态信封** —— 它只会逐字节地回显该服务器签名并交给它的最后一个 `` 信封(首先嵌入在 SSR 页面中,然后在每次响应时刷新)。验证该信封就是证明它未被篡改的手段:
- **篡改 → 拒绝。** 对已签名信封的任何更改 —— 组件 ID、状态 payload 或某个 claim —— 都会无法通过 HMAC-SHA256 验证,并且 `LiveEndpoint` 会在请求到达组件的 `handle()` 之前返回 `400 invalid_signature`。
- **重放 → 409,不会静默重用。** 当 `FileNonceStore`(或另一个 `NonceStoreInterface`)连接到 `SignedXhtmlStateTransferCodec` 时,每个签名都带有一个单次使用的 nonce;第二次解码完全相同的已签名信封会抛出 `ReplayedNonceException`,并且 `LiveEndpoint` 会响应 `409 replay_detected` —— 这是与服务器当前状态的冲突,而不是权限失败,因为该请求在第一次时是真正真实的。
- **CSRF 是一个独立且分离的关卡。** `HmacCsrfGuard` 将 token 绑定到为其签发的精确 `sessionId`/`route` 对;为一个 session 或 route 签发的 token 永远无法在另一个上验证通过,并且 CSRF 失败(`403 csrf`)会在状态信封被解码之前进行检查。
- **授权基于契约,而非环境。** `ContractInteractionAuthorizer` 只允许组件自身契约声明的操作,检查状态的所有者主体(如果有)是否与调用者匹配,并要求派生的 `milpa:component:{name}:{action}` scope —— 伪造看似合理操作名称的攻击者仍然只会得到 `403 action_not_allowed`。
所有这些都不是你必须记住的可选配置:`LiveEndpoint::handle()` 按顺序运行 方法 → CSRF → 签名/重放 → 授权,并将每个失败转换为相应的 HTTP 状态码,而不是抛出异常。
**`@milpa/design` 拓扑注意事项。** HTML 渲染器的 CSS 来自于 `Milpa\Live\Support\MilpaDesign`,它会在你项目根目录下的 `node_modules/@milpa/design` 解析 `@milpa/design` npm 包。如果你的布局不同(例如 monorepo、没有运行 `npm install` 的实验室检出代码),请将 `MILPA_DESIGN_PATH` 设置为 design 包的目录 —— 它优先于 npm 相对查找,并且会被 `MilpaDesign` 的每个方法优先检查。
## 包含内容
| Namespace | 提供的内容 |
|-----------|------------------|
| `Milpa\Live\Http` | `LiveEndpoint` —— 坚固的 HTTP 实时循环入口点 |
| `Milpa\Live\Security` | `HmacStateSigner`、`HmacCsrfGuard`、`FileNonceStore`、`SignedXhtmlStateTransferCodec`、`ContractInteractionAuthorizer`、`AllowListCorsPolicy`、`StaticBearerTokenVerifier` |
| `Milpa\Live\Transport` | `XhtmlStateTransferCodec` —— 未签名的内部传输编解码器 |
| `Milpa\Live\Rendering` | `AutocompleteHtmlRenderer`、`FormPrimitiveHtmlRenderer`、`DashboardHtmlRenderer`、`LatteTemplateRenderer`、`XhtmlComponentCompiler` |
| `Milpa\Live\Adapters\Alpine` | `AlpineRuntimeAdapter` —— 内置的 `ClientRuntimeAdapterInterface` |
| `Milpa\Live\Support` | `Html`(转义助手)、`MilpaDesign`(设计系统路径解析) |
| `Milpa\Live\Contracts\*` | `Security`、`Rendering` 和 `Transport` 接合处 —— `CsrfGuardInterface`、`StateSignerInterface`、`NonceStoreInterface`、`StateTransferCodecInterface`、`ComponentRendererInterface`、`MarkupCompilerInterface`、`TemplateRendererInterface`、`CorsPolicyInterface`、`InteractionAuthorizerInterface`、`TokenVerifierInterface` |
| `Milpa\Live\ValueObjects` | `StateSignature`、`AuthorizationResult`、`CorsDecision` |
每个公共符号都带有 DocBlock。
## 环境要求
- PHP **≥ 8.3** 且安装了 **`ext-dom`** 扩展
- [`milpa/core`](https://packagist.org/packages/milpa/core) **^0.6**
- [`milpa/live`](https://packagist.org/packages/milpa/live) **^0.1**
## 文档
**完整 API 参考:[getmilpa.github.io/live-web](https://getmilpa.github.io/live-web/)** —
直接从源代码 DocBlocks 生成,并配以 Milpa 设计系统。
## 许可证
[Apache-2.0](LICENSE) © Rodrigo Vicente - TeamX Agency.
Milpa 由 **[Rodrigo Vicente - TeamX Agency](https://teamx.agency/?utm_source=github&utm_medium=readme&utm_campaign=milpa&utm_content=live-web)** 设计、构建和维护。
标签:CSRF防护, ffuf, OpenVAS, PHP, Syscall, Web安全加固, Web开发, 前端渲染, 组件库