Client Security / Authentication

Client security for Spring Boot microservices

Spring Middleware provides explicit client security for Spring Boot microservices. Declarative @MiddlewareClient proxies can authenticate to downstream services using OAuth2 client credentials, API keys, or passthrough modes, while keeping outbound request behavior visible and consistent.

OAuth2 client credentials API key authentication Passthrough security Declarative clients Per-method overrides Token caching

What this covers

Declarative middleware clients support several outbound security modes so proxy calls can authenticate consistently to downstream services. Configuration can be defined at client level and refined at method level through annotations.

  • No security for trusted or local scenarios
  • Header passthrough from incoming to outgoing request
  • Static or per-method API key attachment
  • OAuth2 client credentials with token acquisition and caching

What this is not

Client security is not the same as service HTTP security. This page describes how a service authenticates when it calls another service through @MiddlewareClient. Inbound authentication and authorization are covered separately in the service security guide.

  • Outbound concern: how a proxy call is secured.
  • Configuration-driven: behavior comes from both config and annotations.
  • Explicit runtime model: headers and bearer tokens are applied intentionally.

Security modes

Declarative clients map to a small set of outbound security strategies.

NONE

No security is applied to the outbound request.

PASSTHROUGH

A header from the incoming request is copied to the downstream call.

API_KEY

An API key header is attached from class-level or per-method configuration.

OAUTH2_CLIENT_CREDENTIALS

An access token is obtained through client credentials and attached as a bearer token.

Configuration model

Client-wide configuration typically lives under the client prefix. The effective behavior is a combination of configuration and proxy annotations.

Typical YAML

client:
  registry-endpoint: ${REGISTRY_ENDPOINT:http://localhost:8080/registry}
  product:
    security:
      type: OAUTH2_CLIENT_CREDENTIALS
      api-key: ${API_KEY_PRODUCT_SERVICE:default-product-api-key}
      listProducts:
        api-key: ${API_KEY_PRODUCT_SERVICE:default-product-list-api-key}
      oauth2:
        client-id: ${OAUTH2_CLIENT_ID_PRODUCT_SERVICE:product-service}
        client-secret: ${OAUTH2_CLIENT_SECRET_PRODUCT_SERVICE}
        token-uri: ${OAUTH2_TOKEN_URI_PRODUCT_SERVICE:http://keycloak:8080/realms/spring-middleware/protocol/openid-connect/token}

Client-level baseline

Configuration defines the default security mode and base credentials for the proxy.

Method-level overrides

Methods can override class-level API key values or add required OAuth2 scopes when needed.

Final outbound behavior

The runtime analyzer combines configuration and annotations to select the effective security strategy.

Annotations and per-method behavior

The proxy metadata model uses annotations to declare how security should be applied to a client and its methods.

Class-level annotations

  • @MiddlewareContract or @MiddlewareClient defines the security type placeholder
  • @MiddlewarePassthrough declares passthrough header behavior
  • @MiddlewareApiKey defines API key header name and value placeholder
  • @MiddlewareClientCredentials defines token endpoint, client id, and secret

Method-level annotations

  • @MiddlewareApiKeyValue overrides class-level API key value
  • @MiddlewareRequiredScopes declares required OAuth2 scopes for the method
  • Method metadata is resolved separately from the client baseline

Runtime flow

Outbound security is resolved in a small but explicit runtime sequence.

01

Analyze proxy security

ProxySecurityAnalyzer inspects annotations and resolved placeholders to build a client security configuration.

02

Resolve method security

Method metadata contributes scopes, per-method API keys, or a void configuration when nothing extra is needed.

03

Select security applier

SecurityManagerApplier delegates to the correct SecurityApplier based on the selected client type.

04

Modify the outbound request

The selected applier attaches headers, API key values, or bearer tokens to the outbound WebClient call.

Outbound security path

The proxy runtime first resolves security metadata, then applies the selected strategy to the request.

Proxy Interface ProxySecurityAnalyzer client + method metadata WebClient Request Security appliers none • passthrough • api key • client credentials header copy • API key header • bearer token attachment

Passthrough mode

Passthrough forwards a header from the incoming request to the outbound request. This is useful when the downstream service should receive the caller's authentication token.

Configuration

client:
  product:
    security:
      type: PASSTHROUGH

Proxy example

@MiddlewareContract(security = "PASSTHROUGH")
@MiddlewarePassthrough(headerName = "Authorization", required = "true")
public interface ProductClient {

    @GetMapping("/api/v1/products")
    List<ProductDto> listProducts();
}

API key mode

API key mode attaches a configured header to the outbound request. The value can come from a class-level annotation or a method-level override.

Class + method example

@MiddlewareContract(security = "API_KEY")
@MiddlewareApiKey(headerName = "X-API-KEY", value = "${client.product.api-key:}")
public interface ProductClient {

    @GetMapping("/api/v1/products")
    @MiddlewareApiKeyValue("${client.product.list-api-key:}")
    List<ProductDto> listProducts();
}

Behavior

  • Class-level API key config defines the default header and value
  • Method-level value overrides the class default when present
  • SecurityApiKeyApplier adds the resolved header to the request

OAuth2 client credentials

Client credentials mode acquires an access token and sends it as a bearer token in the outbound request.

Configuration example

client:
  product:
    security:
      type: OAUTH2_CLIENT_CREDENTIALS
      oauth2:
        client-id: ${OAUTH2_CLIENT_ID_PRODUCT_SERVICE:product-service}
        client-secret: ${OAUTH2_CLIENT_SECRET_PRODUCT_SERVICE}
        token-uri: ${OAUTH2_TOKEN_URI_PRODUCT_SERVICE:http://keycloak:8080/realms/spring-middleware/protocol/openid-connect/token}

Proxy example

@MiddlewareContract(security = "OAUTH2_CLIENT_CREDENTIALS")
@MiddlewareClientCredentials(
  tokenUri = "${client.product.security.oauth2.token-uri}",
  clientId = "${client.product.security.oauth2.client-id}",
  clientSecret = "${client.product.security.oauth2.client-secret}"
)
public interface ProductClient {

    @GetMapping("/api/v1/products")
    @MiddlewareRequiredScopes({"products.read"})
    List<ProductDto> listProducts();
}

Token management

The built-in OAuth2 client is responsible for token acquisition and caching.

What it does

  • Builds a cache key from token URI, client id, and scopes
  • Requests a token using client credentials via form-encoded POST
  • Caches the access token until expiry
  • Throws a dedicated exception on token acquisition failure

Operational value

This keeps outbound authentication efficient and predictable. Services do not need to hand-roll token lifecycle code for each proxy client.

Best practices

Declarative client security is most effective when secrets remain externalized and method-level overrides are used deliberately.

Keep secrets out of source

Use environment variables or a secrets manager for client secrets and API keys.

Use per-method overrides intentionally

Override class-level defaults only when different endpoints really need distinct API keys or scopes.

Fail fast on passthrough

When passthrough headers are required, validate them early so missing caller context does not create ambiguous downstream failures.