Skip to content

Spring Security Tool Authorization

English | 한국어

The optional Spring Security integration limits which Spring AI tools the model can see and which tools it may execute for the current principal (Authentication). Tool authorization can be used on its own or together with privacy protection.

When both are used, Spring Security authorizes tool exposure and execution, while privacy protection detects PII and controls which original values each tool may receive.

Guarantees

The integration checks authorization at the following stages of a protected tool-calling path.

Stage Guarantee
Before model exposure The authorization policy is evaluated, and denied tools are omitted from the list provided to the model.
Model tool request A tool that was not included in the model's list is not executed, even if the model requests it by name.
Multiple tools in one response Authorization is checked for the complete set of requested tools before execution begins.
Immediately before execution Authorization is checked again for each tool. When privacy protection is also enabled, this happens before any allowed original PII is restored.

When combining authorization with privacy protection, configure tool permissions and original PII disclosure separately:

  • AuthorizationManager<ToolAuthorizationContext> decides whether the current principal may discover or execute a tool.
  • ToolDisclosurePolicy decides which PII entity types an authorized tool may receive as original values.

Add the Spring Boot Starter

Add the Security starter alongside the client starter:

Gradle

dependencies {
    implementation "io.github.ultramancode:spring-ai-privacy-guardrails-spring-security-spring-boot-starter:0.4.0"
}

Maven

<dependency>
    <groupId>io.github.ultramancode</groupId>
    <artifactId>spring-ai-privacy-guardrails-spring-security-spring-boot-starter</artifactId>
    <version>0.4.0</version>
</dependency>

The Security starter can be used without the base Privacy Guardrails starter. Tool authorization uses the Authentication established by the application's existing Spring Security configuration, so it must be available when a request that uses tools begins.

Tool Authorization Only

Add the client starter and an AuthorizationManager<ToolAuthorizationContext> bean, then select toolAuthorization(true) in GuardrailsPolicy. No privacy analyzer is required.

With Privacy Protection

Configure an analyzer as described in privacy setup and select privacy() in the same policy.

Define the Authorization Policy

Register your tool authorization policy as an AuthorizationManager<ToolAuthorizationContext> bean. AuthorizationManager is a standard Spring Security extension interface.

The policy receives Authentication and a ToolAuthorizationContext containing the tool definition and authorization phase. Implement checks on argument values, such as permission to view a particular customer, in the tool or application service.

The following example allows only users with the ROLE_SUPPORT authority to use customerLookup. All other tools are denied. Put the decision logic in GuardrailsToolAuthorizationManager.java:

public final class GuardrailsToolAuthorizationManager implements AuthorizationManager<ToolAuthorizationContext> {
    @Override
    public AuthorizationDecision authorize(Supplier<? extends Authentication> authentication,
            ToolAuthorizationContext context) {
        Authentication currentAuthentication = authentication.get();
        if (currentAuthentication == null || !currentAuthentication.isAuthenticated()) {
            return new AuthorizationDecision(false);
        }

        boolean hasSupportRole = currentAuthentication.getAuthorities().stream()
                .anyMatch(authority -> authority.getAuthority().equals("ROLE_SUPPORT"));

        boolean granted = switch (context.toolDefinition().name()) {
            case "customerLookup" -> hasSupportRole;
            default -> false;
        };
        return new AuthorizationDecision(granted);
    }
}

Register the implementation in an application @Configuration class:

@Bean
AuthorizationManager<ToolAuthorizationContext> toolAuthorizationManager() {
    return new GuardrailsToolAuthorizationManager();
}

Use context.phase() if you need different rules for each phase. Its value is ToolAuthorizationPhase.DEFINITION when building the tool list for the model and ToolAuthorizationPhase.EXECUTION when checking permission to execute a tool.

If the policy returns null or a denied result, the integration omits the tool from the list during the definition phase and rejects the call during the execution phase.

To allow all tools, configure the toolAuthorizationManager bean as follows:

@Bean
AuthorizationManager<ToolAuthorizationContext> toolAuthorizationManager() {
    return (authentication, context) -> new AuthorizationDecision(true);
}

The tool authorization integration captures the request's Authentication before evaluating the policy. This is required even when the policy allows all tools.

Configure the ChatClient

After preparing dependencies and the authorization policy, create the client through GuardrailsChatClientFactory. customerLookupToolCallback is an application-provided tool bean.

@Bean
ChatClient chatClient(ChatModel chatModel, GuardrailsChatClientFactory guardrailsChatClientFactory,
        ToolCallback customerLookupToolCallback) {
    GuardrailsPolicy policy = GuardrailsPolicy.builder()
            .toolAuthorization(true)
            .build();
    return guardrailsChatClientFactory.builder(chatModel, policy)
            .defaultTools(customerLookupToolCallback)
            .build();
}

Verify Authorization

Inject the client into a service handling an authenticated request and run this code:

String response = chatClient.prompt()
        .user("Look up customer CUST-123456.")
        .call()
        .content();

With the example authorization policy, only users with ROLE_SUPPORT may discover and execute customerLookup.

To combine privacy protection with tool authorization, add privacy() to the client policy:

GuardrailsPolicy policy = GuardrailsPolicy.builder()
        .privacy()
        .toolAuthorization(true)
        .build();

If a tool needs original PII, use ToolDisclosurePolicy to specify which entity types it may receive.

Tool Advisor Configuration

A tool-calling advisor executes tools requested by the model and passes their results back to it. ToolCallingAdvisor and the search-enabled ToolSearchToolCallingAdvisor serve this role and implement Spring AI's ToolAdvisor interface.

Factory-created clients can also use ordinary advisors, such as conversation memory or RAG advisors, registered through defaultAdvisors(...) or request-level advisors(...). When combining them with privacy protection, follow the Advisor Configuration so added data passes through privacy inspection.

The factory configures the tool-calling advisor with authorization checks. For clients that use tools:

  • Keep Spring AI's automatic tool-advisor registration enabled. spring.ai.chat.client.tool-calling.enabled is enabled by default. Do not disable it per request with AdvisorParams.toolCallingAdvisorAutoRegister(false).
  • Registering a separate tool-calling advisor (a ToolAdvisor implementation) with defaultAdvisors(...) or request-level advisors(...) is rejected. To change tool calling, pass its builder to the factory as described below.

Custom Tool Advisor

Pass the builder for your chosen tool-calling advisor to guardrailsChatClientFactory.builder(chatModel, policy, toolAdvisorBuilder). The factory connects tool authorization checks when configuring the client.

The following example sets the tool-calling advisor's execution order to 100 on a client using tool authorization alone:

@Bean
ChatClient chatClient(
        ChatModel chatModel,
        GuardrailsChatClientFactory guardrailsChatClientFactory,
        ToolCallback customerLookupToolCallback
) {
    GuardrailsPolicy policy = GuardrailsPolicy.builder()
            .toolAuthorization(true)
            .build();
    ToolCallingAdvisor.Builder<?> toolAdvisorBuilder = ToolCallingAdvisor.builder().advisorOrder(100);
    return guardrailsChatClientFactory.builder(chatModel, policy, toolAdvisorBuilder)
            .defaultTools(customerLookupToolCallback)
            .build();
}

100 is an example value. Choose an execution order that fits the other advisors in your application. Orders incompatible with the protection setup are rejected.

The factory supports ToolCallingAdvisor.Builder<?> and its subclasses. See the Tool Search example for passing a tool-search advisor builder.

Requirements when implementing an advisor or builder

The factory copies the supplied builder, sets an authorization-aware ToolCallingManager on the copy, and builds the advisor. Custom advisors and builders must support this process:

  • A builder returned by copy() must still create the same type of advisor and retain the existing settings.
  • The advisor produced by build() must use the ToolCallingManager set by the factory through toolCallingManager(...) so tool authorization checks apply.
  • Do not implement PriorityOrdered on a tool-calling advisor. It could start tool calling before authorization checks, so the factory rejects it. Set the execution order through the builder's advisorOrder(...) instead.

The following minimal example extends ToolCallingAdvisor. It uses the default tool-calling behavior and implements builder copying and advisor creation.

final class CustomToolCallingAdvisor extends ToolCallingAdvisor {

    private CustomToolCallingAdvisor(ToolCallingManager manager,
            ToolExecutionEligibilityChecker checker, int order, boolean history) {
        super(manager, checker, order, history);
    }

    static final class Builder extends ToolCallingAdvisor.Builder<Builder> {

        @Override
        public Builder copy() {
            return (Builder) super.copy();
        }

        @Override
        protected Builder newCopy() {
            return new Builder();
        }

        @Override
        public CustomToolCallingAdvisor build() {
            ToolCallingManager manager = getToolCallingManager();
            return new CustomToolCallingAdvisor(
                    manager,
                    getToolExecutionEligibilityChecker(),
                    getAdvisorOrder(),
                    isConversationHistoryEnabled()
            );
        }
    }
}

The inherited copy() copies the standard advisor settings into the builder created by newCopy(). If your builder adds configuration fields, copy those fields in your copy() implementation as well.

Pass this builder to the factory when creating the client:

ChatClient chatClient = guardrailsChatClientFactory
        .builder(chatModel, policy, new CustomToolCallingAdvisor.Builder().advisorOrder(100))
        .defaultTools(customerLookupToolCallback)
        .build();

ToolCallingManager Selection

ToolCallingManager handles the actual execution of tools. When a tool policy bean is present, the starter's default setup uses Spring AI's DefaultToolCallingManager bean.

The default setup fails at startup if that bean is missing or multiple candidates are present. To select a ToolCallingManager explicitly, provide a SpringSecurityToolBoundary bean as shown below.

Custom ToolCallingManager

A ToolCallback is an object that invokes a tool. When delegating execution, the library wraps each application tool callback with a permission recheck and places the wrapped callbacks in the tool options of the Prompt passed to your custom ToolCallingManager. Each wrapper checks permission before invoking the callback it wraps.

When combining authorization with privacy protection, the library automatically adds this permission-checking wrapper around the callback wrapped by PrivacyToolCallbackFactory. Your custom ToolCallingManager must invoke the outermost callback supplied in the Prompt.

This example delegates execution to Spring AI's default implementation, DefaultToolCallingManager. Passing the received Prompt and model response unchanged lets this implementation use the callbacks in the Prompt, preserving the permission recheck.

@Bean
SpringSecurityToolBoundary springSecurityToolBoundary(
        ToolCallingManager delegate,
        AuthorizationManager<ToolAuthorizationContext> authorizationManager
) {
    ToolCallingManager customManager = new ToolCallingManager() {
        @Override
        public List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions options) {
            return delegate.resolveToolDefinitions(options);
        }

        @Override
        public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse response) {
            return delegate.executeToolCalls(prompt, response);
        }
    };

    return SpringSecurityToolBoundary.builder(customManager, authorizationManager)
            .build();
}

The example receives Spring AI's DefaultToolCallingManager bean through the delegate parameter, declared as ToolCallingManager. If you already have a custom ToolCallingManager, pass that object in place of customManager.

In this example, delegate.executeToolCalls(prompt, response) finds the callbacks and passes along ToolContext, which carries additional data needed for tool execution. The delegating code does not need to implement callback selection or context passing separately.

Any custom ToolCallingManager you connect must execute tools through the callbacks in the supplied Prompt. These callbacks recheck permission immediately before tool execution, so calling the original tool separately skips this check. Pass the tool context as well to preserve data needed for execution, including privacy protection when enabled.

The starter's factories use the SpringSecurityToolBoundary bean shown above when configuring clients. Create a client as shown in Configure the ChatClient and call tools through that client.

Without the Spring Boot Starter

Depend on spring-ai-privacy-guardrails-chat-client and the Security module, then connect the authorization boundary to GuardrailsChatClientFactory. This example selects authorization only:

SpringSecurityToolBoundary boundary = SpringSecurityToolBoundary.builder(
        ToolCallingManager.builder().build(), authorizationManager).build();
ToolAuthorizationChatClientFactory authorizationFactory = ToolAuthorizationChatClientFactory.builder()
        .toolAuthorizationBoundary(boundary)
        .build();
GuardrailsChatClientFactory guardrailsChatClientFactory = GuardrailsChatClientFactory.builder()
        .toolAuthorization(authorizationFactory::prepareToolAuthorization)
        .defaultPolicy(GuardrailsPolicy.builder()
                .toolAuthorization(true)
                .build())
        .build();
ChatClient chatClient = guardrailsChatClientFactory.builder(chatModel)
        .defaultTools(customerLookupToolCallback)
        .build();

To add privacy or inspection, supply its service and select the feature in the policy.

Tool Search is an optional Spring AI feature that lets a model search for the tools it needs.

Add org.springframework.ai:spring-ai-tool-search-advisor, using the version managed by your application's Spring AI BOM.

The following example combines tool authorization, Tool Search, and privacy protection by passing the tool-search advisor's builder to the factory. ToolIndex is the tool search index provided as a bean by the application.

@Bean
ChatClient chatClient(
        ChatModel chatModel,
        GuardrailsChatClientFactory guardrailsChatClientFactory,
        ToolIndex toolIndex
) {
    GuardrailsPolicy policy = GuardrailsPolicy.builder()
            .privacy()
            .toolAuthorization(true)
            .build();
    return guardrailsChatClientFactory.builder(chatModel, policy,
                    ToolSearchToolCallingAdvisor.builder().toolIndex(toolIndex))
            .build();
}

For authorization alone, omit .privacy() from the policy. Register tools directly with defaultTools(...) or request-specific tools(...). If tools need original PII, set toolDisclosurePolicy(...) in the privacy settings.

Tool Search also requires a conversation ID on each request. The following example registers a tool and passes the application's conversationId to a client that also uses privacy protection:

String response = chatClient.prompt()
        .user("Find customer CUST-123456.")
        .tools(customerLookupToolCallback)
        .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, conversationId))
        .call()
        .content();

With this setup, only authorized tools registered for the request are searchable, and permission is checked again immediately before a selected tool executes. When privacy protection is also used, detected PII in the search query is replaced with opaque tokens before the search runs. These replacement strings do not directly reveal the original values.

The library allows the tool that performs the search. The application's authorization policy applies to the tools being searched. Tool replacements and unsupported additions during a request are rejected before execution.

If you configure Tool Search directly with a ToolCallingManager that does not apply authorization, denied tools may also become searchable. See the Threat Model for considerations when configuring this yourself.

Blocking, Reactive, and Asynchronous Context

When a tool runs on a different thread during a request, its authorization policy still receives the user's Authentication obtained at the start of that request.

  • Blocking calls: Authentication is obtained from the Spring Security context when the request begins.
  • Streaming: The Reactor security context takes precedence. The calling thread's authentication is used only when no Reactor security context is present. A Reactor security context that is registered but empty causes the request to be rejected.
  • Asynchronous calls: If the ChatClient invocation itself starts on another thread, propagate Spring Security authentication to it. Use DelegatingSecurityContextExecutorService for the executor running the task to propagate authentication. The same requirement applies to virtual threads.

A request with registered tools is rejected if it has no Authentication.

The captured Authentication represents the request identity. Authorization is still evaluated again at execution time, but detecting a permission revocation during a long-running request requires the application policy to consult current external state rather than relying only on authorities captured in the object.

The library cleans up request authorization state when a blocking call completes or fails, or when a stream completes, fails, or is cancelled.

Handling Denials

The tool authorization boundary throws ToolAuthorizationDeniedException when it rejects a request. Its code() returns a ToolAuthorizationDenialCode identifying the cause:

try {
    chatClient.prompt().user(userMessage).call().content();
} catch (ToolAuthorizationDeniedException ex) {
    logger.warn("Tool authorization denied: {}", ex.code());
    throw ex;
}
Code Meaning
AUTHENTICATION_REQUIRED A request with registered tools has no Authentication.
POLICY_DENIED The authorization policy denied tool execution.
NO_AUTHORIZATION_DECISION The authorization policy returned null for tool execution.
TOOL_NOT_EXPOSED The model requested a tool that was not exposed for the request.
CALLBACK_CHANGED A tool callback was added or replaced in an unsupported way during the request.
INVALID_AUTHORIZATION_SESSION The authorization session is missing, invalid, or closed.
UNMANAGED_TOOL_ADVISOR The request is missing this client's managed tool advisor or uses a separate one.
INVALID_TOOL_CALL_OPTIONS Tool execution was requested without ToolCallingChatOptions.

phase() returns an Optional<ToolAuthorizationPhase> containing DEFINITION for denials during tool-list construction or EXECUTION for denials during execution checks. It is empty for denials before either phase, such as missing authentication or an unmanaged tool advisor.

The exception extends Spring Security's AuthorizationDeniedException. When the policy returns a denied result, getAuthorizationResult() preserves that result.

Denied or undecided tools are omitted from the model's tool list during the definition phase. This filtering does not throw a denial exception. Exceptions thrown directly by the application's AuthorizationManager propagate unchanged.

Compatibility

Component Minimum supported version
Java 17
Spring AI 2.0.0
Spring Boot 4.0.0
Spring Security 7.0.0

We continue to verify compatibility and support new releases of Java, Spring AI, Spring Boot and Spring Security as they become available. The Boot starter uses the Spring Security version selected by the Spring Boot BOM.