From bf8bb15b2d409e1f2b5f13692e52f0e687dc1906 Mon Sep 17 00:00:00 2001 From: tangzhiqiang <23572557+tangzhiqiang@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:28:46 +0800 Subject: [PATCH] feat(starter): wire host TaskAuthorizationProvider into the request handler Hosts receiving A2A traffic (e.g. a workbench behind an MSB gateway) need a business-owned validation point before the agent executor runs. The A2A Java SDK already ships an operation-level authorization SPI; this exposes it in the starter: when a TaskAuthorizationProvider bean is present, the autoconfigured request handler wraps the default handler with the SDK's AuthorizationRequestHandlerDecorator so checkCreate guards message send/stream, checkRead/checkWrite guard task query, cancel, and subscribe, and rejections surface as standard A2A error envelopes. The decorator also carries CDI Instance fields, so it stays behind the SpringCompatibleRequestHandler facade like the delegate. Without such a bean the server is unchanged. Concrete use case: secondary authentication specifications require every provider-side call to be validated against signature headers (app-id/timestamp/sign-alg/sign/signature) before business execution. The A2AController already places all request headers into the ServerCallContext state, so a provider implementation reads them directly from the context and applies its validation steps. Includes context tests proving a rejecting provider stops the request before the executor and that the handler still assembles without one, plus API reference documentation (zh/en) with a usage example. Signed-off-by: tangzhiqiang <23572557+tangzhiqiang@users.noreply.github.com> --- docs/en/API_REFERENCE.md | 30 ++++++- docs/zh/API_REFERENCE.md | 30 ++++++- .../engine/spring/A2AAutoConfiguration.java | 26 +++++- .../spring/A2AAutoConfigurationTest.java | 82 +++++++++++++++++++ 4 files changed, 164 insertions(+), 4 deletions(-) diff --git a/docs/en/API_REFERENCE.md b/docs/en/API_REFERENCE.md index 7486c16..663fef6 100644 --- a/docs/en/API_REFERENCE.md +++ b/docs/en/API_REFERENCE.md @@ -731,11 +731,39 @@ Auto-configures the following beans (all `@ConditionalOnMissingBean`, so you can | `pushStore` | `PushNotificationConfigStore` | Push notification config storage | | `agentExecutorPool` | `ExecutorService` | Thread pool for agent execution (8 threads, daemon) | | `eventBusProcessor` | `MainEventBusProcessor` | Event bus processor | -| `requestHandler` | `RequestHandler` | Default request handler | +| `requestHandler` | `RequestHandler` | Default request handler; operation-level authorization is enabled automatically when a `TaskAuthorizationProvider` bean exists | | `restHandler` | `RestHandler` | REST protocol handler | | `a2aController` | `A2AController` | Spring MVC controller for message and task endpoints | | `a2aSlashActionAliasController` | `A2ASlashActionAliasController` | Optional public controller for slash-style action aliases | +### Server-side request authorization (TaskAuthorizationProvider) + +Declare a `TaskAuthorizationProvider` bean (`@Component` or `@Bean`) to inject business validation +(caller identity, request legality, ...) into every A2A operation **before** the agent executor runs: + +```java +@Component +public class WorkbenchRequestGuard implements TaskAuthorizationProvider { + @Override + public boolean checkCreate(ServerCallContext ctx, TaskOperation op) throws A2AError { + // message:send / message:stream (new task creation) arrive here + return ctx.getUser().isAuthenticated(); // false or an A2AError rejects the request + } + @Override public boolean checkRead(ServerCallContext ctx, String taskId, TaskOperation op) { ... } + @Override public boolean checkWrite(ServerCallContext ctx, String taskId, TaskOperation op) { ... } + @Override public boolean isTaskRecorded(String taskId) { ... } + @Override public void recordOwnership(ServerCallContext ctx, String taskId, TaskOperation op) { ... } +} +``` + +- `checkCreate` guards `message:send` / `message:stream`; `checkRead` guards task query/list; + `checkWrite` guards task cancel; `recordOwnership`/`isTaskRecorded` track task ownership. +- `ServerCallContext` exposes `getUser()` (authenticated user), `getRequestedExtensions()` + (the A2A-Extensions request header), and `getState()` (transport state). +- Rejections return a standard A2A error envelope to the caller (`checkCreate` returning false maps + to a task-not-found error; throwing a more specific `A2AError` subclass carries a custom reason). +- Without such a bean the server behaves exactly as before (no authorization decorator is wired). + ### A2AController Spring MVC controller that exposes A2A REST endpoints: diff --git a/docs/zh/API_REFERENCE.md b/docs/zh/API_REFERENCE.md index be4b1cb..9a6a2e7 100644 --- a/docs/zh/API_REFERENCE.md +++ b/docs/zh/API_REFERENCE.md @@ -684,11 +684,39 @@ a2at: | `pushStore` | `PushNotificationConfigStore` | 推送通知配置存储 | | `agentExecutorPool` | `ExecutorService` | 智能体执行线程池(8 线程,守护线程) | | `eventBusProcessor` | `MainEventBusProcessor` | 事件总线处理器 | -| `requestHandler` | `RequestHandler` | 默认请求处理器 | +| `requestHandler` | `RequestHandler` | 默认请求处理器;存在 `TaskAuthorizationProvider` Bean 时自动启用操作级鉴权 | | `restHandler` | `RestHandler` | REST 协议处理器 | | `a2aController` | `A2AController` | 消息和任务端点的 Spring MVC 控制器 | | `a2aSlashActionAliasController` | `A2ASlashActionAliasController` | 可选的斜杠动作别名公共控制器 | +### 服务端请求鉴权(TaskAuthorizationProvider) + +宿主声明一个 `TaskAuthorizationProvider` Bean(`@Component` 或 `@Bean`),即可在每个 A2A 操作进入 +智能体执行器**之前**注入业务校验(调用方身份、请求合法性等): + +```java +@Component +public class WorkbenchRequestGuard implements TaskAuthorizationProvider { + @Override + public boolean checkCreate(ServerCallContext ctx, TaskOperation op) throws A2AError { + // message:send / message:stream(新任务创建)走这里 + return ctx.getUser().isAuthenticated(); // 返回 false 或抛 A2AError 即拒绝 + } + @Override public boolean checkRead(ServerCallContext ctx, String taskId, TaskOperation op) { ... } + @Override public boolean checkWrite(ServerCallContext ctx, String taskId, TaskOperation op) { ... } + @Override public boolean isTaskRecorded(String taskId) { ... } + @Override public void recordOwnership(ServerCallContext ctx, String taskId, TaskOperation op) { ... } +} +``` + +- `checkCreate` 守卫 `message:send` / `message:stream`;`checkRead` 守卫任务查询/列表; + `checkWrite` 守卫任务取消;`recordOwnership`/`isTaskRecorded` 用于任务归属跟踪。 +- `ServerCallContext` 提供 `getUser()`(认证用户)、`getRequestedExtensions()`(A2A-Extensions 请求头) + 和 `getState()`(传输状态)。 +- 拒绝以标准 A2A 错误信封返回调用方(`checkCreate` 返回 false 时为任务不存在错误; + 抛出更具体的 `A2AError` 子类可携带自定义原因)。 +- 未声明该 Bean 时行为与之前完全一致(不装配鉴权装饰器)。 + ### A2AController 暴露 A2A REST 端点的 Spring MVC 控制器: diff --git a/spring-boot-starter/src/main/java/dev/openan/workflow/engine/spring/A2AAutoConfiguration.java b/spring-boot-starter/src/main/java/dev/openan/workflow/engine/spring/A2AAutoConfiguration.java index c67a6aa..8041024 100644 --- a/spring-boot-starter/src/main/java/dev/openan/workflow/engine/spring/A2AAutoConfiguration.java +++ b/spring-boot-starter/src/main/java/dev/openan/workflow/engine/spring/A2AAutoConfiguration.java @@ -29,10 +29,12 @@ import java.util.concurrent.TimeUnit; import org.a2aproject.sdk.server.AgentCardCacheMetadata; import org.a2aproject.sdk.server.agentexecution.AgentExecutor; +import org.a2aproject.sdk.server.auth.TaskAuthorizationProvider; import org.a2aproject.sdk.server.config.A2AConfigProvider; import org.a2aproject.sdk.server.events.InMemoryQueueManager; import org.a2aproject.sdk.server.events.MainEventBus; import org.a2aproject.sdk.server.events.MainEventBusProcessor; +import org.a2aproject.sdk.server.requesthandlers.AuthorizationRequestHandlerDecorator; import org.a2aproject.sdk.server.requesthandlers.DefaultRequestHandler; import org.a2aproject.sdk.server.requesthandlers.RequestHandler; import org.a2aproject.sdk.server.tasks.BasePushNotificationSender; @@ -44,6 +46,7 @@ import org.a2aproject.sdk.transport.rest.handler.RestHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -70,6 +73,12 @@ *
Optional host-side request validation: declare a {@link TaskAuthorizationProvider} bean and + * every A2A operation is offered to it before the agent executor runs. {@code checkCreate} guards + * message send/stream (new task creation), {@code checkRead}/{@code checkWrite} guard task + * query/cancel, and rejection returns a standard A2A error envelope to the caller. Without such a + * bean the server behaves exactly as before. + * *
The partner only needs to provide an {@link AgentExecutor} implementation as a
* {@code @Component} or {@code @Bean}. Set {@code a2at.server.enabled=true} to enable the
* server-side autoconfiguration.
@@ -189,7 +198,8 @@ public RequestHandler requestHandler(
InMemoryQueueManager qm,
PushNotificationConfigStore pushStore,
MainEventBusProcessor proc,
- ExecutorService pool) {
+ ExecutorService pool,
+ ObjectProvider