Enterprise JavaJava

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:
                - "http://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=http://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=http://localhost:8081
spring.ai.mcp.client.type=SYNC

spring.security.oauth2.client.provider.authserver.issuer-uri=http://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=http://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 http://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.

Download
You can download the full source code of this example here: spring ai oauth2 mcp authorization

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button