diff --git a/daprdocs/content/en/java-sdk-docs/java-client/_index.md b/daprdocs/content/en/java-sdk-docs/java-client/_index.md index c162b16bed..5f33eb41e3 100644 --- a/daprdocs/content/en/java-sdk-docs/java-client/_index.md +++ b/daprdocs/content/en/java-sdk-docs/java-client/_index.md @@ -632,6 +632,124 @@ try (DaprClient client = new DaprClientBuilder().build()) { Learn more about the [Dapr Java SDK packages available to add to your Java applications](https://dapr.github.io/java-sdk/). +## Security + +### App API Token Authentication + +The building blocks like pubsub, input bindings, or jobs require Dapr to make incoming calls to your application, you can secure these requests using [Dapr App API Token Authentication]({{% ref app-api-token.md %}}). This ensures that only Dapr can invoke your application's endpoints. + +#### Understanding the two tokens + +Dapr uses two different tokens for securing communication. See [Properties]({{% ref properties.md %}}) for detailed information about both tokens: + +- **`DAPR_API_TOKEN`** (Your app → Dapr sidecar): Automatically handled by the Java SDK when using `DaprClient` +- **`APP_API_TOKEN`** (Dapr → Your app): Requires server-side validation in your application + +The examples below show how to implement server-side validation for `APP_API_TOKEN`. + +#### Implementing server-side token validation + +When using gRPC protocol, implement a server interceptor to capture the metadata. + +```java +import io.grpc.Context; +import io.grpc.Contexts; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; + +public class SubscriberGrpcService extends AppCallbackGrpc.AppCallbackImplBase { + public static final Context.Key METADATA_KEY = Context.key("grpc-metadata"); + + // gRPC interceptor to capture metadata + public static class MetadataInterceptor implements ServerInterceptor { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, + Metadata headers, + ServerCallHandler next) { + Context contextWithMetadata = Context.current().withValue(METADATA_KEY, headers); + return Contexts.interceptCall(contextWithMetadata, call, headers, next); + } + } + + // Your service methods go here... +} +``` + +Register the interceptor when building your gRPC server: + +```java +Server server = ServerBuilder.forPort(port) + .intercept(new SubscriberGrpcService.MetadataInterceptor()) + .addService(new SubscriberGrpcService()) + .build(); +server.start(); +``` + +Then, in your service methods, extract the token from metadata: + +```java +@Override +public void onTopicEvent(DaprAppCallbackProtos.TopicEventRequest request, + StreamObserver responseObserver) { + try { + // Extract metadata from context + Context context = Context.current(); + Metadata metadata = METADATA_KEY.get(context); + + if (metadata != null) { + String apiToken = metadata.get( + Metadata.Key.of("dapr-api-token", Metadata.ASCII_STRING_MARSHALLER)); + + // Validate token accordingly + } + + // Process the request + // ... + + } catch (Throwable e) { + responseObserver.onError(e); + } +} +``` + +#### Using with HTTP endpoints + +For HTTP-based endpoints, extract the token from the headers: + +```java +@RestController +public class SubscriberController { + + @PostMapping(path = "/endpoint") + public Mono handleRequest( + @RequestBody(required = false) byte[] body, + @RequestHeader Map headers) { + return Mono.fromRunnable(() -> { + try { + // Extract the token from headers + String apiToken = headers.get("dapr-api-token"); + + // Validate token accordingly + + // Process the request + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } +} +``` + +#### Examples + +For working examples with pubsub, bindings, and jobs: +- [PubSub with App API Token Authentication](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/pubsub#app-api-token-authentication-optional) +- [Bindings with App API Token Authentication](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/bindings/http#app-api-token-authentication-optional) +- [Jobs with App API Token Authentication](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/jobs#app-api-token-authentication-optional) + ## Related links - [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples) diff --git a/daprdocs/content/en/java-sdk-docs/java-client/properties.md b/daprdocs/content/en/java-sdk-docs/java-client/properties.md index 2b26d87bdf..87eb7a99c4 100644 --- a/daprdocs/content/en/java-sdk-docs/java-client/properties.md +++ b/daprdocs/content/en/java-sdk-docs/java-client/properties.md @@ -32,11 +32,16 @@ When these variables are set, the client will automatically use them to connect | `DAPR_GRPC_PORT` | The gRPC port for the Dapr sidecar (legacy, `DAPR_GRPC_ENDPOINT` takes precedence) | `50001` | | `DAPR_HTTP_PORT` | The HTTP port for the Dapr sidecar (legacy, `DAPR_HTTP_ENDPOINT` takes precedence) | `3500` | -### API Token +### API Tokens + +Dapr supports two types of API tokens for securing communication: | Environment Variable | Description | Default | |---------------------|-------------|---------| -| `DAPR_API_TOKEN` | API token for authentication between app and Dapr sidecar. This is the same token used by the Dapr runtime for API authentication. For more details, see [Dapr API token authentication](https://docs.dapr.io/operations/security/api-token/) and [Environment variables reference](https://docs.dapr.io/reference/environment/#dapr_api_token). | `null` | +| `DAPR_API_TOKEN` | API token for authenticating requests **from your app to the Dapr sidecar**. The Java SDK automatically includes this token in requests when using `DaprClient`. | `null` | +| `APP_API_TOKEN` | API token for authenticating requests **from Dapr to your app**. When set, Dapr includes this token in the `dapr-api-token` header/metadata when calling your application (for pubsub subscribers, input bindings, or job triggers). Your application must validate this token. | `null` | + +For implementation examples, see [App API Token Authentication]({{% ref java-client#app-api-token-authentication %}}). For more details, see [Dapr API token authentication](https://docs.dapr.io/operations/security/api-token/). ### gRPC Configuration diff --git a/examples/src/main/java/io/dapr/examples/bindings/http/README.md b/examples/src/main/java/io/dapr/examples/bindings/http/README.md index cd9ce42062..74a4176ad5 100644 --- a/examples/src/main/java/io/dapr/examples/bindings/http/README.md +++ b/examples/src/main/java/io/dapr/examples/bindings/http/README.md @@ -75,6 +75,22 @@ b95e7ad31707 confluentinc/cp-zookeeper:7.4.4 "/etc/confluent/dock…" 5 da ``` Click [here](https://github.com/wurstmeister/kafka-docker) for more information about the kafka broker server. +### App API Token Authentication (Optional) + +Dapr supports API token authentication to secure communication between Dapr and your application. When using input bindings, Dapr makes incoming calls to your app, and you can validate these requests using the `APP_API_TOKEN`. + +For detailed implementation with gRPC interceptors, see the [PubSub README App API Token Authentication section](../pubsub/README.md#app-api-token-authentication-optional). + +For HTTP-based apps, check the `dapr-api-token` header in incoming requests. For more details, see the [Dapr App API Token Authentication documentation](https://docs.dapr.io/operations/security/app-api-token/). + +**Quick setup:** + +```bash +# Export tokens before running the following `dapr run` commands. +export APP_API_TOKEN="your-app-api-token" +export DAPR_API_TOKEN="your-dapr-api-token" +``` + ### Running the Input binding sample The input binding sample uses the Spring Boot´s DaprApplication class for initializing the `InputBindingController`. In `InputBindingExample.java` file, you will find the `InputBindingExample` class and the `main` method. See the code snippet below: diff --git a/examples/src/main/java/io/dapr/examples/jobs/README.md b/examples/src/main/java/io/dapr/examples/jobs/README.md index 2877b31fba..4b899ac4a6 100644 --- a/examples/src/main/java/io/dapr/examples/jobs/README.md +++ b/examples/src/main/java/io/dapr/examples/jobs/README.md @@ -44,6 +44,22 @@ cd examples Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized. +### App API Token Authentication (Optional) + +Dapr supports API token authentication to secure communication between Dapr and your application. When using the Jobs API, Dapr makes incoming calls to your app at job trigger time, and you can validate these requests using the `APP_API_TOKEN`. + +For detailed implementation with gRPC interceptors, see the [PubSub README App API Token Authentication section](../pubsub/README.md#app-api-token-authentication-optional). + +For more details, see the [Dapr App API Token Authentication documentation](https://docs.dapr.io/operations/security/app-api-token/). + +**Quick setup:** + +```bash +# Export tokens before running the following `dapr run` commands. +export APP_API_TOKEN="your-app-api-token" +export DAPR_API_TOKEN="your-dapr-api-token" +``` + ### Running the example This example uses the Java SDK Dapr client in order to **Schedule and Get** Jobs. diff --git a/examples/src/main/java/io/dapr/examples/pubsub/README.md b/examples/src/main/java/io/dapr/examples/pubsub/README.md index 4fb16290ca..6aa5d401de 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/README.md +++ b/examples/src/main/java/io/dapr/examples/pubsub/README.md @@ -41,6 +41,88 @@ cd examples Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized. +### App API Token Authentication (Optional) + +Dapr supports API token authentication to secure communication between Dapr and your application. This feature is useful for numerous APIs like pubsub, bindings, and jobs building blocks where Dapr makes incoming calls to your app. + +For more details, see the [Dapr App API Token Authentication documentation](https://docs.dapr.io/operations/security/app-api-token/). + +#### How it works + +When `APP_API_TOKEN` is set, Dapr includes the token in the gRPC metadata header `dapr-api-token` when calling your app. Your app can validate this token to authenticate requests from Dapr. + +#### Setting up tokens + +Set a dapr annotation or simply export the environment variables before running your Dapr applications: + +```bash +# Token for your app to authenticate requests FROM Dapr +export APP_API_TOKEN="your-app-api-token" + +# Token for Dapr client to authenticate requests TO Dapr sidecar +export DAPR_API_TOKEN="your-dapr-api-token" +``` + +#### Using with gRPC Subscriber + +The gRPC subscriber example includes a `MetadataInterceptor` (see `SubscriberGrpcService.java`) that captures the `dapr-api-token` from incoming requests: + +```java +public class SubscriberGrpcService extends AppCallbackGrpc.AppCallbackImplBase { + public static final Context.Key METADATA_KEY = Context.key("grpc-metadata"); + + // gRPC interceptor to capture metadata + public static class MetadataInterceptor implements ServerInterceptor { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + Context contextWithMetadata = Context.current().withValue(METADATA_KEY, headers); + return Contexts.interceptCall(contextWithMetadata, call, headers, next); + } + } +} +``` + +Then in your service methods, you can extract and validate the token: + +```java +Context context = Context.current(); +Metadata metadata = METADATA_KEY.get(context); +String apiToken = metadata.get(Metadata.Key.of("dapr-api-token", Metadata.ASCII_STRING_MARSHALLER)); + +// Validate token accordingly +``` + +#### Using with HTTP Subscriber + +For HTTP-based endpoints, extract the token from the headers: + +```java +@RestController +public class SubscriberController { + + @PostMapping(path = "/endpoint") + public Mono handleRequest( + @RequestBody(required = false) byte[] body, + @RequestHeader Map headers) { + return Mono.fromRunnable(() -> { + try { + // Extract the token from headers + String apiToken = headers.get("dapr-api-token"); + + // Validate token accordingly + + // Process the request + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } +} +``` + +Then use the standard `dapr run` commands shown in the sections below. The subscriber will validate incoming requests from Dapr using `APP_API_TOKEN`, and both applications will authenticate to Dapr using `DAPR_API_TOKEN`. + ### Running the publisher The publisher is a simple Java application with a main method that uses the Dapr gRPC Client to publish 10 messages to a specific topic. diff --git a/examples/src/main/java/io/dapr/examples/pubsub/grpc/Subscriber.java b/examples/src/main/java/io/dapr/examples/pubsub/grpc/Subscriber.java index 4c9cff939a..8c3c570887 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/grpc/Subscriber.java +++ b/examples/src/main/java/io/dapr/examples/pubsub/grpc/Subscriber.java @@ -48,8 +48,9 @@ public static void main(String[] args) throws Exception { int port = Integer.parseInt(cmd.getOptionValue("port")); //start a grpc server - Server server = ServerBuilder.forPort(port) - .addService(new SubscriberGrpcService()) + Server server = ServerBuilder.forPort(port) + .intercept(new SubscriberGrpcService.MetadataInterceptor()) + .addService(new SubscriberGrpcService()) .addService(new BulkSubscriberGrpcService()) .build(); server.start(); diff --git a/examples/src/main/java/io/dapr/examples/pubsub/grpc/SubscriberGrpcService.java b/examples/src/main/java/io/dapr/examples/pubsub/grpc/SubscriberGrpcService.java index d454280530..642d9e1b91 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/grpc/SubscriberGrpcService.java +++ b/examples/src/main/java/io/dapr/examples/pubsub/grpc/SubscriberGrpcService.java @@ -16,6 +16,12 @@ import com.google.protobuf.Empty; import io.dapr.v1.AppCallbackGrpc; import io.dapr.v1.DaprAppCallbackProtos; +import io.grpc.Context; +import io.grpc.Contexts; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; import io.grpc.stub.StreamObserver; import java.util.ArrayList; @@ -27,6 +33,17 @@ public class SubscriberGrpcService extends AppCallbackGrpc.AppCallbackImplBase { private final List topicSubscriptionList = new ArrayList<>(); + public static final Context.Key METADATA_KEY = Context.key("grpc-metadata"); + // gRPC interceptor to capture metadata + public static class MetadataInterceptor implements ServerInterceptor { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + Context contextWithMetadata = Context.current().withValue(METADATA_KEY, headers); + return Contexts.interceptCall(contextWithMetadata, call, headers, next); + } + } + @Override public void listTopicSubscriptions(Empty request, StreamObserver responseObserver) { @@ -50,6 +67,30 @@ public void listTopicSubscriptions(Empty request, public void onTopicEvent(DaprAppCallbackProtos.TopicEventRequest request, StreamObserver responseObserver) { try { + try { + Context context = Context.current(); + Metadata metadata = METADATA_KEY.get(context); + + if (metadata != null) { + System.out.println("Metadata found in context"); + String apiToken = metadata.get(Metadata.Key.of("dapr-api-token", Metadata.ASCII_STRING_MARSHALLER)); + if (apiToken != null) { + System.out.println("API Token extracted: " + apiToken); + } else { + System.out.println("No 'dapr-api-token' found in metadata"); + } + System.out.println("All metadata:"); + for (String key : metadata.keys()) { + String value = metadata.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)); + System.out.println("key: " + key + ": " + value); + } + } else { + System.out.println("No metadata found in context"); + } + } catch (Exception e) { + System.out.println(" Error extracting metadata: " + e.getMessage()); + } + String data = request.getData().toStringUtf8().replace("\"", ""); System.out.println("Subscriber got: " + data); DaprAppCallbackProtos.TopicEventResponse response = DaprAppCallbackProtos.TopicEventResponse.newBuilder()