Aller au contenu principal
Article

Sécuriser les outils IA avec Spring Security

Quand un LLM peut appeler vos méthodes Java, la sécurité devient critique. Dans cet article, nous combinons Spring AI et Spring Security pour contrôler l'accès aux outils IA avec @PreAuthorize et une authentification HTTP Basic.

7 min de lecture
spring-aiiallmjavaspring-bootspring-security
spring-aiiallm

Dans l'article précédent, nous avons vu comment permettre à un LLM d'appeler des fonctions Java via le Function Calling. Mais dans un contexte d'entreprise, une question cruciale se pose : qui a le droit d'utiliser quel outil ?

Un utilisateur avec un rôle basique devrait-il pouvoir consulter les soldes bancaires ? Un outil qui modifie des données devrait-il être accessible à tous ? Le module fc-tools-secure montre comment combiner Spring AI et Spring Security pour sécuriser les outils IA.

A- L'architecture sécurisée

Le pattern est élégant : au lieu d'exécuter la logique directement dans les fonctions tools, on la délègue à un service Spring sécurisé avec @PreAuthorize.

LLM → Tool (Function/Method) → DemoService (@PreAuthorize) → Logique métier

Si l'utilisateur authentifié n'a pas le rôle requis, Spring Security bloque l'appel avant même que la logique métier ne s'exécute.

B- Configuration Spring Security

@Configuration
@EnableMethodSecurity
@EnableWebSecurity
public class SecurityConfig {
 
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http)
            throws Exception {
        return http
                .httpBasic(Customizer.withDefaults())
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth ->
                    auth.anyRequest().authenticated())
                .build();
    }
 
    @Bean
    UserDetailsService userDetailsService() {
        var admin = User.builder()
                .username("admin")
                .password("{noop}password")
                .roles("USER", "ADMIN")
                .build();
 
        var user = User.builder()
                .username("user")
                .password("{noop}password")
                .roles("USER")
                .build();
 
        return new InMemoryUserDetailsManager(admin, user);
    }
}

Points clés

  • @EnableMethodSecurity : active la sécurité au niveau des méthodes (@PreAuthorize)
  • HTTP Basic : authentification simple pour la démo
  • Deux utilisateurs : admin (ROLE_ADMIN + ROLE_USER) et user (ROLE_USER uniquement)

C- Le service sécurisé : DemoService

@Service
public class DemoService {
 
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    public TollResponse getUserAccountByName(ToolRequest toolRequest) {
        var name = toolRequest.name();
        var userAccount = USER_ACCOUNTS.stream()
                .filter(account ->
                    account.name().equalsIgnoreCase(name))
                .findFirst()
                .orElse(null);
        if (userAccount == null) {
            return new TollResponse(String.format(
                "Le compte de %s n'a pas été trouvé", name));
        }
        return new TollResponse(String.format(
            "Le compte de %s a un solde de %d et est de type %s",
            userAccount.name(), userAccount.sold(),
            userAccount.accountType()));
    }
 
    @PreAuthorize("hasAnyRole('ROLE_USER, ROLE_ADMIN')")
    public String getCurrentDateTime() {
        return LocalDateTime.now()
                .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    }
 
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    public String getCurrentJavaVersion() {
        return System.getProperty("java.version");
    }
}

Matrice des permissions

OutilRôle requisadminuser
getUserAccountByNameROLE_ADMINOKKO
getCurrentDateTimeROLE_USER ou ROLE_ADMINOKOK
getCurrentJavaVersionROLE_ADMINOKKO

D- Function as Tool sécurisé

Les beans tools délèguent au DemoService sécurisé :

@Configuration
public class FunctionAsTools {
 
    private final DemoService demoService;
 
    public FunctionAsTools(DemoService demoService) {
        this.demoService = demoService;
    }
 
    @Bean
    @Description("Get user account by name")
    public Function<DemoService.ToolRequest, DemoService.TollResponse>
            getUserAccountByName() {
        return demoService::getUserAccountByName;
    }
 
    @Bean
    @Description("Get the current date and time")
    public Supplier<String> getCurrentDateTime() {
        return demoService::getCurrentDateTime;
    }
}

Notez la différence avec la version non sécurisée : ici, les beans sont de simples références de méthode (demoService::getUserAccountByName) qui délèguent au service protégé.

E- Method as Tool sécurisé

@Component
public class MethodAsTools {
 
    private final DemoService demoService;
 
    public MethodAsTools(DemoService demoService) {
        this.demoService = demoService;
    }
 
    @Tool(description = "Get current Java version")
    String getCurrentJavaVersion() {
        System.out.printf("Call getCurrentJavaVersion tool%n");
        return demoService.getCurrentJavaVersion();
    }
}

Même pattern : la méthode @Tool délègue à DemoService, où @PreAuthorize vérifie les permissions.

F- Le contrôleur REST

@RestController
@RequestMapping("/chat")
public class DemoController {
 
    private final ChatClient chatClient;
    private final DemoService demoService;
 
    public DemoController(ChatClient.Builder chatClientBuilder,
                          DemoService demoService) {
        this.chatClient = chatClientBuilder.build();
        this.demoService = demoService;
    }
 
    @GetMapping
    public String ask(String message) {
        return chatClient.prompt(message)
                .toolNames("getUserAccountByName",
                            "getCurrentDateTime")
                .tools(new MethodAsTools(this.demoService))
                .call()
                .content();
    }
}

G- Tester la sécurité

# Admin peut tout faire
curl -u admin:password \
  "http://localhost:8080/chat?message=Quel+est+le+solde+de+Dubois+?"
# → "Le compte de Dubois a un solde de 100 de type Courant"
 
# User ne peut PAS accéder aux comptes (ROLE_ADMIN requis)
curl -u user:password \
  "http://localhost:8080/chat?message=Quel+est+le+solde+de+Dubois+?"
# → AccessDeniedException (403)
 
# User PEUT demander la date (ROLE_USER suffit)
curl -u user:password \
  "http://localhost:8080/chat?message=Quelle+heure+est-il+?"
# → "Il est actuellement 2026-04-09T14:30:00"
 
# Sans authentification → 401
curl "http://localhost:8080/chat?message=Bonjour"
# → 401 Unauthorized

H- Pattern de sécurité

Le pattern appliqué ici est réutilisable dans tout projet Spring AI :

1. Créer un @Service avec la logique métier
2. Annoter chaque méthode avec @PreAuthorize
3. Créer les tools (Function/Method) qui délèguent au service
4. Configurer Spring Security (@EnableMethodSecurity)

Ce pattern suit le principe de séparation des responsabilités :

  • Les tools gèrent l'interface avec le LLM (description, paramètres)
  • Le service gère la logique métier et la sécurité
  • La configuration définit les utilisateurs et les rôles

Conclusion

La combinaison de Spring AI et Spring Security offre un modèle de sécurité robuste pour les outils IA. L'annotation @PreAuthorize au niveau du service garantit que les contrôles d'accès sont appliqués quelle que soit la manière dont l'outil est appelé.

Points clés :

  • Déléguer la logique au service sécurisé, pas dans les tools
  • @PreAuthorize protège chaque méthode individuellement
  • Le LLM ne contourne jamais la sécurité, le contrôle est côté serveur
  • Le pattern est compatible avec OAuth2, JWT ou tout autre mécanisme Spring Security

Dans le prochain article, nous verrons l'orchestration multi-agents : comment coordonner plusieurs agents IA spécialisés.

J'espère que cet article vous a été utile. Merci de l'avoir lu.

Pour en savoir plus :


Série « Spring AI en Action »

  1. Introduction à Spring AI
  2. ChatClient API : Premiers pas avec l'API
  3. Chat Memory : contexte conversationnel
  4. RAG : Pipeline d'ingestion
  5. RAG : Du Naïf à l'Avancé
  6. Function Calling
  7. Tools + Security
  8. Orchestration multi-agents
  9. Model Context Protocol (MCP)
PartagerXLinkedIn