Class McpServerOAuthAccessTokenAudienceProvider
- All Implemented Interfaces:
io.meeds.oauth2.server.configuration.plugin.OAuthAccessTokenAudienceProvider
That refusal is what keeps an excluded user from adding the server
to an external MCP client at all. Without it they complete the whole OAuth
flow — discovery, dynamic client registration, login, consent — obtain a
token, and only then meet the door's 401: they end up owning a connector
that looks broken. With it the token request fails with
access_denied, and nothing is ever recorded as connected. A refresh
goes through the same customizer, so an audience narrowed later bites at
the next refresh as well as, through the door, at the very next request.
The refusal has to be raised, not expressed by returning null.
OAuthAccessTokenCustomizerService.computeJwtAudiences takes the
first non-empty answer among the registered providers and only fails the
request when every one of them abstains — and the authorization server
ships a second provider, OAuthAccessTokenAudienceTokenRequestProvider,
which answers with the resource parameter of the authorization
request (RFC 8707) whenever that value is one of
OAuthSettingService.getAllowedAudiences(). This addon registers its
own URL as exactly such an allowed audience, and MCP clients do send
resource. So a null here would merely abstain, that provider would
name the MCP URL in its place, and the excluded user would get a token
anyway. Throwing OAuth2AuthenticationException instead ends the
stream before any other provider is consulted, and the customizer
propagates it as the token endpoint's error response.
The refusal depends on this provider being consulted first.
computeJwtAudiences is a short-circuiting stream, so the throw below
happens only while this provider is reached before
OAuthAccessTokenAudienceTokenRequestProvider — which would otherwise
answer from resource and end the stream. That ordering is an
accident of OAuthAccessTokenCustomizerService's comparator,
(p1, p2) -> p2.getOrder() - p1.getOrder(): this provider keeps the
default HIGHEST_PRECEDENCE (Integer.MIN_VALUE), the
resource provider declares LOWEST_PRECEDENCE
(Integer.MAX_VALUE), and MAX_VALUE - MIN_VALUE overflows to
-1 — so this provider sorts first because the subtraction
overflows. Computed without overflow, that descending sort would put
the resource provider first and the refusal below would never be
reached; of the two overflow-free rewrites, only an ascending
Integer.compare(p1.getOrder(), p2.getOrder()) — the Spring
Ordered convention the two constants' names imply — preserves the
gate. Making the refusal independent of position is the authorization
server's to do (consult every provider before choosing, or an explicit veto
in the SPI); until then the aggregate test, which registers both providers
through the real addProvider, is the tripwire: if it goes red after
a change to that comparator, the MCP token gate has stopped working and the
test is right.
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidinit()Registers this provider with the authorization server's access token customizer, which consults it on every access token it issues.provideAudiences(org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext context) Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface io.meeds.oauth2.server.configuration.plugin.OAuthAccessTokenAudienceProvider
getOrder
-
Constructor Details
-
McpServerOAuthAccessTokenAudienceProvider
public McpServerOAuthAccessTokenAudienceProvider()
-
-
Method Details
-
init
@PostConstruct public void init()Registers this provider with the authorization server's access token customizer, which consults it on every access token it issues. -
provideAudiences
public List<String> provideAudiences(org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext context) - Specified by:
provideAudiencesin interfaceio.meeds.oauth2.server.configuration.plugin.OAuthAccessTokenAudienceProvider- Parameters:
context- the access token being issued- Returns:
- this MCP server's URL as the token's audience when the token carries an MCP tool scope and either has no end user (a client-credentials grant) or has one the MCP server is enabled for; null for a token without an MCP scope, which lets another provider answer
- Throws:
org.springframework.security.oauth2.core.OAuth2AuthenticationException- withaccess_deniedfor an MCP token whose end user is outside the MCP audience, or whose context carries no principal at all — a grant this code does not know fails closed, as a refusal rather than an abstention another provider could override
-