Notes pratiques : Recette Spring AI : Sécuriser un serveur MCP avec OAuth
Guide opérationnel des notes pratiques : Recette Spring AI – Sécuriser un serveur MCP avec OAuth : contrats, vérifications et emplacements de code prêts à l’emploi pour les équipes qui implémentent ce modèle.
Les notes suivantes reconstituent une approche pratique pour aborder le sujet « Spring AI Recipe : Sécuriser un serveur MCP avec OAuth ». L’accent est mis sur les contrats, les vérifications et les placeholders de code à insérer, plutôt que sur une présentation motivante. Lors de la phase d’aperçu, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stockages de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans devoir lire l’ensemble du système.
Création du serveur d’autorisation
La phase de création du serveur d’autorisation fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et une note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Fournissez des outils dotés de schémas restreints et de labels explicites indiquant les effets secondaires. Les hôtes doivent savoir quels appels modifient l’état avant d’approuver automatiquement.
implementation 'org.springaicommunity:mcp-authorization-server:0.1.14'
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth ->
auth.anyRequest().authenticated())
.formLogin(withDefaults())
.with(
McpAuthorizationServerConfigurer.mcpAuthorizationServer(),
withDefaults())
.build();
}
}
spring:
application:
name: recipes-authorization-server
security:
oauth2:
authorizationserver:
client:
default-client:
token:
access-token-time-to-live: 1h
registration:
client-id: "myclient"
client-secret: "{noop}mysecret"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "authorization_code"
redirect-uris:
- "http://localhost:6274/oauth/callback"
- "http://127.0.0.1:6274/oauth/callback"
scopes:
- general-access
- meteorology
- administration
user:
name: craig
password: letmein
server:
port: 9999
Sécuriser le serveur MCP
La phase de sécurisation du serveur MCP fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé. Exposez des outils dotés de schémas restreints et de labels explicites indiquant les effets secondaires. Les hôtes doivent savoir quels appels modifient l’état avant d’approuver automatiquement.
implementation 'org.springaicommunity:mcp-server-security:0.1.14'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9999
@Configuration
@EnableWebSecurity
class McpSecurityConfig {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUrl;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth ->
auth.anyRequest().authenticated())
.with(
McpServerOAuth2Configurer.mcpServerOAuth2(),
(mcpAuthorization) -> {
mcpAuthorization.authorizationServer(issuerUrl);
}
)
.build();
}
}
Test du serveur MCP
La phase de test du serveur MCP fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définites des vérifications de succès et refusez toute mise à jour partielle silencieuse. Exposez des outils dotés de schémas restreints et de labels explicites indiquant leurs effets secondaires. Les hôtes doivent savoir quels appels modifient l’état avant d’approuver automatiquement. La phase de test du serveur MCP fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent se trouver en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système.
Sécuriser les outils individuels
Pour l’étape des outils individuels sécurisés, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’une mise en forme ultérieure. Authentifiez au niveau du gateway et réautorisez au niveau du plan de données. Un token porteur seul ne constitue pas une frontière entre les tenants.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUrl;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/mcp").permitAll();
auth.anyRequest().authenticated();
})
.with(
McpServerOAuth2Configurer.mcpServerOAuth2(),
(mcpAuthorization) -> {
mcpAuthorization.authorizationServer(issuerUrl);
}
)
.build();
}
}
@PreAuthorize("hasAuthority('SCOPE_meteorology')")
@McpTool(
name = "get-weather-for-zipcode",
description = "Gets the weather for a given zipcode",
annotations = @McpTool.McpAnnotations(
openWorldHint = false,
destructiveHint = false,
idempotentHint = true))
Weather getWeatherForZipcode(
@McpToolParam(description = "The zipcode to get weather for")
String zipcode) {
var context = SecurityContextHolder.getContext();
var username = context.getAuthentication().getName();
return new Weather(
zipcode,
"Raining cats and dogs",
78.0f,
username);
}
Liste de contrôle opérationnelle
Pour l’étape de la liste de contrôle opérationnelle, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché.
Enregistrez les temps d’exécution ainsi que le coût des jetons ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts permet d’éviter des factures inattendues lorsque le parcours passe de l’environnement de démonstration à des environnements partagés.
Authentifiez-vous au niveau du gateway et réautorisez-vous au niveau du plan de données. Un simple jeton porteur ne constitue pas une frontière entre les tenants.
Rédigez un guide de procédures succinct : comment rotationner les clés, comment vider la file d’attente, comment annuler la dernière ingestion.
Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du schéma.
Authentifiez-vous au niveau du gateway et réautorisez-vous au niveau du plan de données. Un simple jeton porteur ne constitue pas une frontière entre les tenants.
Au préalable de promouvoir le stack, figez les versions, conservez une transcription « or » pour le chemin critique et confirmez les étapes de rollback. Les environnements partagés nécessitent des limites de débit, des vérifications de location ainsi qu’un responsable clair pour la rotation des secrets. Préférez une fiabilité banale à des démonstrations ingénieuses ponctuelles.
Note de lot pour 01496ca9e17e : gardez les clés du fournisseur hors du repo, fixez un plafond pour les tokens par session et stockez les transcriptions à côté des fichiers de test afin que les remplacements ultérieurs de modèles restent comparables.
Pour la note de renforcement du niveau 0, définissez les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute exécution partielle silencieuse.
Détail de renforcement 0/819 : mesurez le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décidez si vous souhaitez conserver le changement en vous basant sur un ensemble de questions prédéfini plutôt que sur des observations anecdotiques.
Lors de la première étape de la note de renforcement, écrivez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications de code ultérieures. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stockages de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système.
Détail de renforcement 1/819 : mesurez le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décidez si vous souhaitez conserver le changement en vous basant sur un ensemble de questions prédéfini plutôt que sur des observations anecdotiques.
La deuxième étape de la note de renforcement fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé.
Détail de renforcement 2/819 : mesurez le temps d’exécution, la classe d’erreur et l’utilisation des tokens pour cette note, puis décidez si vous souhaitez conserver la modification en vous basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.
Pour la phase 3 des notes de renforcement, définissez les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des jetons ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés.
Détail de renforcement 3/819 : mesurez le temps d’exécution réel, la catégorie de l’erreur et la consommation de jetons pour cette note, puis décidez s’il convient de conserver la modification en vous basant sur un ensemble de questions prédéfini plutôt que sur des observations subjectives.
Lors de la réalisation de l’étape 4 des notes de renforcement, notez d’abord les conditions contractuelles : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations apportées ultérieurement.
Détail de renforcement 4/819 : mesurez le temps d’exécution, la catégorie de l’erreur et l’utilisation des tokens pour cette note, puis décidez si vous conservez la modification en vous basant sur un ensemble de questions prédéfinies plutôt que sur des observations subjectives.
L’étape 5 des notes de renforcement fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Recueillez un exemple idéal de fonctionnement, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses.
Détail de renforcement 5/819 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.
Pour l’étape 6 de la note de renforcement, définir les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conserver la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système.
Détail de renforcement 6/819 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.