Skip to content

Commit db978c1

Browse files
committed
authorization-server: add spring.ai.mcp.authorizationserver.dynamic-client-registration.enabled property
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent 528b18b commit db978c1

5 files changed

Lines changed: 129 additions & 5 deletions

File tree

mcp-authorization-server-boot/src/main/java/org/springaicommunity/mcp/security/authorizationserver/boot/McpAuthorizationServerAutoConfiguration.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.springframework.boot.autoconfigure.AutoConfiguration;
2323
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2425
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2526
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity;
2627
import org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties;
@@ -58,14 +59,17 @@
5859
*/
5960
@AutoConfiguration(before = OAuth2AuthorizationServerAutoConfiguration.class)
6061
@ConditionalOnDefaultWebSecurity
61-
@EnableConfigurationProperties(OAuth2AuthorizationServerProperties.class)
62+
@EnableConfigurationProperties({ OAuth2AuthorizationServerProperties.class,
63+
McpOAuth2AuthorizationServerProperties.class })
6264
class McpAuthorizationServerAutoConfiguration {
6365

6466
@Bean
6567
@Order(Ordered.HIGHEST_PRECEDENCE)
66-
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) {
68+
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
69+
McpOAuth2AuthorizationServerProperties properties) {
6770
return http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
6871
.with(mcpAuthorizationServer(), mcp -> {
72+
mcp.dynamicClientRegistration(properties.getDynamicClientRegistration().isEnabled());
6973
mcp.authorizationServer(authzServer -> {
7074
http.securityMatcher(new OrRequestMatcher(authzServer.getEndpointsMatcher(),
7175
PathPatternRequestMatcher.withDefaults().matcher("/.well-known/openid-configuration")));
@@ -85,7 +89,9 @@ SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) {
8589

8690
@Bean
8791
@ConditionalOnMissingBean
88-
RegisteredClientRepository registeredClientRepository(OAuth2AuthorizationServerProperties properties) {
92+
@ConditionalOnProperty(prefix = McpOAuth2AuthorizationServerProperties.CONFIG_PREFIX,
93+
name = "dynamic-client-registration.enabled", havingValue = "true", matchIfMissing = true)
94+
RegisteredClientRepository dcrRegisteredClientRepository(OAuth2AuthorizationServerProperties properties) {
8995
var clients = new OAuth2AuthorizationServerPropertiesMapper(properties).asRegisteredClients();
9096
// default generated client: the repository cannot be empty, but we support DCR
9197
// so we should be able to add clients after it's wired. This client cannot be
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2026-2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springaicommunity.mcp.security.authorizationserver.boot;
18+
19+
import org.springframework.boot.context.properties.ConfigurationProperties;
20+
21+
/**
22+
* @author Daniel Garnier-Moiroux
23+
*/
24+
@ConfigurationProperties(value = McpOAuth2AuthorizationServerProperties.CONFIG_PREFIX)
25+
class McpOAuth2AuthorizationServerProperties {
26+
27+
public static final String CONFIG_PREFIX = "spring.ai.mcp.authorizationserver";
28+
29+
private DynamicClientRegistration dynamicClientRegistration = new DynamicClientRegistration();
30+
31+
public DynamicClientRegistration getDynamicClientRegistration() {
32+
return dynamicClientRegistration;
33+
}
34+
35+
public void setDynamicClientRegistration(DynamicClientRegistration dynamicClientRegistration) {
36+
this.dynamicClientRegistration = dynamicClientRegistration;
37+
}
38+
39+
public static class DynamicClientRegistration {
40+
41+
/**
42+
* Enable dynamic client registration.
43+
*/
44+
private boolean enabled = true;
45+
46+
public boolean isEnabled() {
47+
return enabled;
48+
}
49+
50+
public void setEnabled(boolean enabled) {
51+
this.enabled = enabled;
52+
}
53+
54+
}
55+
56+
}

mcp-authorization-server-boot/src/test/java/org/springaicommunity/mcp/security/authorizationserver/boot/McpAuthorizationServerAutoConfigurationTests.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
import org.springframework.security.web.SecurityFilterChain;
3535
import static org.assertj.core.api.Assertions.assertThat;
3636

37+
/**
38+
* @author Daniel Garnier-Moiroux
39+
*/
3740
class McpAuthorizationServerAutoConfigurationTests {
3841

3942
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
@@ -86,7 +89,7 @@ void registeredClientProperties() {
8689
}
8790

8891
@Test
89-
void useDefaultRegisteredClientRepository() {
92+
void useDefaultDcrRegisteredClientRepository() {
9093
this.contextRunner.withUserConfiguration(CustomRegisteredClientRepositoryConfiguration.class).run((context) -> {
9194
assertThat(context).hasSingleBean(RegisteredClientRepository.class);
9295
RegisteredClientRepository repository = context.getBean(RegisteredClientRepository.class);
@@ -95,6 +98,42 @@ void useDefaultRegisteredClientRepository() {
9598
});
9699
}
97100

101+
@Test
102+
void dynamicClientRegistrationDisabled() {
103+
this.contextRunner
104+
.withPropertyValues("spring.ai.mcp.authorizationserver.dynamic-client-registration.enabled=false")
105+
.run((context) -> {
106+
assertThat(context).hasFailed()
107+
.getFailure()
108+
.hasMessageContaining(
109+
"No qualifying bean of type 'org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository'");
110+
});
111+
}
112+
113+
@Test
114+
void dynamicClientRegistrationDisabledWithRegistrations() {
115+
this.contextRunner.withPropertyValues(
116+
"spring.ai.mcp.authorizationserver.dynamic-client-registration.enabled=false",
117+
"spring.security.oauth2.authorizationserver.client.test-client.registration.client-id=my-client",
118+
"spring.security.oauth2.authorizationserver.client.test-client.registration.client-secret={noop}secret",
119+
"spring.security.oauth2.authorizationserver.client.test-client.registration.client-authentication-methods=client_secret_basic",
120+
"spring.security.oauth2.authorizationserver.client.test-client.registration.authorization-grant-types=client_credentials")
121+
.run((context) -> {
122+
RegisteredClientRepository repository = context.getBean(RegisteredClientRepository.class);
123+
assertThat(repository.findByClientId("my-client")).isNotNull();
124+
assertThat(repository.findByClientId("default")).isNull();
125+
});
126+
}
127+
128+
@Test
129+
void dynamicClientRegistrationDisabledWithCustomRepository() {
130+
this.contextRunner.withUserConfiguration(CustomRegisteredClientRepositoryConfiguration.class)
131+
.withPropertyValues("spring.ai.mcp.authorizationserver.dynamic-client-registration.enabled=false")
132+
.run((context) -> {
133+
assertThat(context).hasNotFailed();
134+
});
135+
}
136+
98137
@Configuration(proxyBeanMethods = false)
99138
static class CustomSecurityConfiguration {
100139

mcp-authorization-server/src/main/java/org/springaicommunity/mcp/security/authorizationserver/config/McpAuthorizationServerConfigurer.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public class McpAuthorizationServerConfigurer
5151

5252
private Customizer<OAuth2AuthorizationServerConfigurer> authServerCustomizer = Customizer.withDefaults();
5353

54+
private boolean supportDynamicClientRegistration = true;
55+
5456
public static McpAuthorizationServerConfigurer mcpAuthorizationServer() {
5557
return new McpAuthorizationServerConfigurer();
5658
}
@@ -70,6 +72,16 @@ public McpAuthorizationServerConfigurer authorizationServer(
7072
return this;
7173
}
7274

75+
/**
76+
* Enable or disable dynamic client registration (DCR).
77+
* @param enabled turn on DCR when true, off otherwise. Defaults to true.
78+
* @return The {@link McpAuthorizationServerConfigurer} for further configuration.
79+
*/
80+
public McpAuthorizationServerConfigurer dynamicClientRegistration(boolean enabled) {
81+
this.supportDynamicClientRegistration = enabled;
82+
return this;
83+
}
84+
7385
@Override
7486
public void init(HttpSecurity http) {
7587
http.authorizeHttpRequests(
@@ -79,7 +91,9 @@ public void init(HttpSecurity http) {
7991
authServer.authorizationServerMetadataEndpoint(Customizer.withDefaults());
8092
OAuth2TokenGenerator<?> tokenGenerator = getTokenGenerator(http);
8193
authServer.tokenGenerator(tokenGenerator);
82-
authServer.clientRegistrationEndpoint(cr -> cr.openRegistrationAllowed(true));
94+
if (this.supportDynamicClientRegistration) {
95+
authServer.clientRegistrationEndpoint(cr -> cr.openRegistrationAllowed(true));
96+
}
8397
this.authServerCustomizer.customize(authServer);
8498
});
8599

samples/sample-authorization-server/src/main/resources/application.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
spring:
22
application:
33
name: sample-authorization-server
4+
5+
# To disable dynamic client registration:
6+
# ai:
7+
# mcp:
8+
# authorizationserver:
9+
# dynamic-client-registration:
10+
# enabled: true
11+
12+
# To use with a pre-existing client:
413
security:
514
oauth2:
615
authorizationserver:

0 commit comments

Comments
 (0)