MCP Authorization With Spring AI and OAuth2
As AI systems evolve, integrating external tools securely has become a critical concern. MCP (Model Context Protocol) enables AI applications to communicate with domain-specific services; however, ensuring that only trusted clients can access these services requires a robust security model. Spring AI combined with OAuth2, provides a flexible foundation for handling both user authentication and authorization.
In this article, we’ll demonstrate how to secure MCP servers and clients using Spring AI and OAuth2.
1. Architecture Overview
The system comprises distinct components, each responsible for a specific aspect of security and communication. Together, they form a comprehensive workflow for securing MCP with OAuth2.
We will build an example with three key components:
- MCP Client: A Spring AI application that consumes the MCP server using OAuth2 tokens.
- Authorization Server: Issues OAuth2 tokens to authenticated clients.
- Protected MCP Server: Exposes business data via MCP and validates tokens.
2. Setting up the Authorization Server
The authorization server is responsible for issuing OAuth2 tokens to authenticated clients. The MCP server later uses these tokens to authorize incoming requests. We can use Spring Authorization Server.
pom.xml dependencies
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId> </dependency>
Our authorization server runs on port 9000 and manages OAuth2 tokens. We’ll configure it to register one client (mcp-client
) with multiple grant types.
server: port: 9000 spring: security: user: name: jcguser password: password oauth2: authorizationserver: client: oidc-client: registration: client-id: "mcp-client" client-secret: "{noop}mcp-secret" client-authentication-methods: - "client_secret_basic" authorization-grant-types: - "authorization_code" - "client_credentials" - "refresh_token" redirect-uris: - "https://localhost:8080/authorize/oauth2/code/authserver" scopes: - "openid" - "profile" - "mcp.read" - "mcp.write"
This configuration sets up an OAuth2 authorization server with a single registered client (mcp-client
). It supports the client_credentials
grant (ideal for server-to-server communication such as MCP clients) and the authorization_code
grant (used when an end user authenticates via browser and exchanges an authorization code for an access token).
3. Protecting the MCP Server
The MCP server is where business data is exposed. The MCP server will run as a resource server that validates tokens against the authorization server. It exposes tools that AI applications can query.
pom.xml dependencies
<properties> <spring-ai.version>1.0.2</spring-ai.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
The application.properties
file shown below defines the server port, connects the resource server to the authorization server, and enables MCP-specific settings. This ensures the server can both expose business data and validate incoming OAuth2 tokens.
application.properties
spring.application.name=mcp-protected-server server.port=8081 spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:9000 spring.ai.mcp.server.enabled=true spring.ai.mcp.server.name=mcp-book-server spring.ai.mcp.server.version=1.0.0 spring.ai.mcp.server.stdio=false
This configuration runs the MCP server on port 8081
and ensures that all requests are authorized using OAuth2 tokens issued by the authorization server.
With the MCP server configured, the next step is to define the tools it will expose. These tools provide operations that clients can invoke securely via OAuth2. We’ll define this in a simple service class.
@Service public class BookToolService { public record BookResult(String title, String author, int year, String genre) { } @Tool(description = "Get details about a book by title") public BookResult getBookDetails( @ToolParam(description = "The title of the book") String title) { // Dummy data (in real case, fetch from DB or external API) return switch (title.toLowerCase()) { case "the hobbit" -> new BookResult("The Hobbit", "J.R.R. Tolkien", 1937, "A fantasy novel about Bilbo Baggins' adventure with dwarves to reclaim treasure guarded by a dragon."); case "1984" -> new BookResult("1984", "George Orwell", 1949, "A dystopian novel depicting a totalitarian regime that uses surveillance and propaganda to control society."); case "to kill a mockingbird" -> new BookResult("To Kill a Mockingbird", "Harper Lee", 1960, "A classic novel exploring racial injustice and moral growth in the American South during the 1930s."); case "brave new world" -> new BookResult("Brave New World", "Aldous Huxley", 1932, "A dystopian novel that envisions a technologically advanced society driven by consumerism and conformity."); default -> new BookResult(title, "Unknown", 0, "No details available for this book in the sample dataset."); }; } @Tool(description = "Search books by author name") public BookResult searchBooksByAuthor( @ToolParam(description = "Author name") String author) { // Example data return new BookResult("Animal Farm", author, 1945, "Political Satire"); } }
Each method is annotated with @Tool
and @ToolParam
, making them discoverable in the MCP ecosystem. All requests are validated with OAuth2 tokens before execution, ensuring secure access.
4. Setting Up the MCP Client
The MCP client is a Spring AI application running on port 8080
. It obtains OAuth2 tokens from the authorization server and uses them to query the protected MCP server. Token handling is managed automatically through Spring Security’s WebClient integration.
pom.xml dependencies
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-ollama</artifactId> </dependency>
Configuring Application Properties
These properties ensure the client can securely request tokens from the authorization server and communicate with the protected MCP server.
spring.application.name=mcp-client server.port=8080 spring.ai.mcp.client.sse.connections.server1.url=https://localhost:8081 spring.ai.mcp.client.type=SYNC spring.security.oauth2.client.provider.authserver.issuer-uri=https://localhost:9000 spring.security.oauth2.client.registration.authserver.client-id=mcp-client spring.security.oauth2.client.registration.authserver.client-secret=mcp-secret spring.security.oauth2.client.registration.authserver.authorization-grant-type=authorization_code spring.security.oauth2.client.registration.authserver.provider=authserver spring.security.oauth2.client.registration.authserver.scope=openid,profile,mcp.read,mcp.write spring.security.oauth2.client.registration.authserver.redirect-uri={baseUrl}/login/oauth2/code/{registrationId} spring.security.oauth2.client.registration.authserver-client-credentials.client-id=mcp-client spring.security.oauth2.client.registration.authserver-client-credentials.client-secret=mcp-secret spring.security.oauth2.client.registration.authserver-client-credentials.authorization-grant-type=client_credentials spring.security.oauth2.client.registration.authserver-client-credentials.provider=authserver spring.security.oauth2.client.registration.authserver-client-credentials.scope=mcp.read,mcp.write # Ollama local configuration spring.ai.ollama.chat.model=mxbai-embed-large spring.ai.ollama.init.pull-model-strategy=when_missing spring.ai.ollama.init.chat.include=true spring.ai.ollama.chat.base-url=https://localhost:11434 spring.ai.langchain4j.ollama.timeout=60s
This configuration allows the client to use both authorization_code (user login) and client_credentials (machine-to-machine) flows.
WebClient & Security Configuration
To interact with the MCP server, the client application uses a WebClient
configured with OAuth2 security. This ensures that all outgoing requests automatically include valid access tokens, either from the authorization code flow or client credentials flow.
@Configuration public class WebClientSecurityConfig { @Bean WebClient.Builder webClientBuilder(OAuth2TokenExchangeFilter filterFunction) { return WebClient.builder() .apply(filterFunction.configuration()); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) .oauth2Client(Customizer.withDefaults()) .csrf(CsrfConfigurer::disable) .build(); } }
This configuration wires WebClient
with OAuth2 token support and disables CSRF for simplicity.
To ensure secure access to the MCP server, we define a custom filter that automatically applies OAuth2 tokens to outgoing requests. This filter manages both the authorization code flow for user-initiated requests and the client credentials flow for machine-to-machine communication.
@Component public class OAuth2TokenExchangeFilter implements ExchangeFilterFunction { private final ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialTokenProvider = new ClientCredentialsOAuth2AuthorizedClientProvider(); private final ServletOAuth2AuthorizedClientExchangeFilterFunction delegate; private final ClientRegistrationRepository clientRegistrationRepository; private static final String AUTHORIZATION_CODE_CLIENT_REGISTRATION_ID = "authserver"; private static final String CLIENT_CREDENTIALS_CLIENT_REGISTRATION_ID = "authserver-client-credentials"; public OAuth2TokenExchangeFilter(OAuth2AuthorizedClientManager clientManager, ClientRegistrationRepository clientRegistrationRepository) { this.delegate = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientManager); this.delegate.setDefaultClientRegistrationId(AUTHORIZATION_CODE_CLIENT_REGISTRATION_ID); this.clientRegistrationRepository = clientRegistrationRepository; } @Override public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) { if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes) { return this.delegate.filter(request, next); } else { var accessToken = getClientCredentialsAccessToken(); var requestWithToken = ClientRequest.from(request) .headers(headers -> headers.setBearerAuth(accessToken)) .build(); return next.exchange(requestWithToken); } } private String getClientCredentialsAccessToken() { var clientRegistration = this.clientRegistrationRepository .findByRegistrationId(CLIENT_CREDENTIALS_CLIENT_REGISTRATION_ID); var authRequest = OAuth2AuthorizationContext.withClientRegistration(clientRegistration) .principal(new AnonymousAuthenticationToken("client-credentials-client", "client-credentials-client", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))) .build(); return this.clientCredentialTokenProvider.authorize(authRequest).getAccessToken().getTokenValue(); } public Consumer<WebClient.Builder> configuration() { return builder -> builder.defaultRequest(this.delegate.defaultRequest()).filter(this); } }
ChatClient Configuration
The ChatClient
bean is configured to work with MCP tools, allowing the Spring AI client to issue prompts that invoke operations.
@Bean ChatClient chatClient(ChatClient.Builder chatClientBuilder, List<McpSyncClient> mcpClients) { return chatClientBuilder.defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpClients)) .build(); }
This integrates the MCP client into the Spring AI chat pipeline, enabling AI-driven queries to leverage the book tools.
Controller
@RestController public class BookController { private final ChatClient chatClient; public BookController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping("/book") public String getBookInfo(@RequestParam String title, @RegisteredOAuth2AuthorizedClient("authserver") OAuth2AuthorizedClient authorizedClient) { String prompt = String.format("Retrieve details about the book titled '%s' using the available book tools.", title); return chatClient.prompt() .user(prompt) .call() .content(); } }
The controller exposes an endpoint /book
that accepts a title, generates a query for the MCP Book server, and responds with AI-enriched book information.
5. Running the Example
To run the system successfully, start by launching the Authorization server on port 9000
, which is responsible for issuing OAuth2 tokens to authenticated clients. Next, run the MCP Server on port 8090
, where the book-related tools are registered and all incoming requests are validated against OAuth2 tokens.
Finally, start the MCP Client on port 8080
, which acts as a Spring AI application that communicates with the MCP server using the tokens it obtains from the Authorization server. Once all three components are up and running, open your browser and navigate to https://localhost:8080/book?title=1984
.
You will be prompted to log in with your configured credentials. After authentication, the MCP Client will securely request book data from the MCP server. If everything has been configured correctly, you will receive a secure AI-generated book response, powered by the registered MCP tools and protected by OAuth2 authorization.
6. Conclusion
In this article, we showed how to secure an MCP server and client using Spring AI and OAuth2, with an Authorization Server issuing tokens, a protected MCP server validating requests, and an MCP client managing secure access. This architecture offers a scalable and reliable way for AI applications to safely integrate with enterprise data systems.
7. Download the Source Code
This article discusses how to implement OAuth2-based authorization for MCP using Spring AI.
You can download the full source code of this example here: spring ai oauth2 mcp authorization