Spring Boot cookbook · Intermediate

Authenticate machine clients with API keys in Spring Security

Allow server-to-server clients to call your API with hashed, revocable API keys.

Approach

Add a filter to the SecurityFilterChain that reads X-API-Key, hashes it (SHA-256), looks up an active client, and sets an Authentication with the client's authorities. Store only hashes, show keys once on creation, and support multiple keys per client for rotation.

Solution

public class ApiKeyAuthFilter extends OncePerRequestFilter {
    private final ApiClientRepository clients;
    public ApiKeyAuthFilter(ApiClientRepository clients) { this.clients = clients; }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws ServletException, IOException {
        String key = req.getHeader("X-API-Key");
        if (key != null) {
            String hash = HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")   // wrap NoSuchAlgorithmException in real code
                    .digest(key.getBytes(StandardCharsets.UTF_8)));
            clients.findActiveByKeyHash(hash).ifPresent(client -> {
                var authorities = client.scopes().stream().map(s -> new SimpleGrantedAuthority("SCOPE_" + s)).toList();
                var auth = new UsernamePasswordAuthenticationToken("client:" + client.id(), null, authorities);
                SecurityContextHolder.getContext().setAuthentication(auth);
            });
        }
        chain.doFilter(req, res);
    }
}

@Bean
SecurityFilterChain machineApi(HttpSecurity http, ApiClientRepository clients) throws Exception {
    return http.securityMatcher("/api/partner/**")
            .addFilterBefore(new ApiKeyAuthFilter(clients), UsernamePasswordAuthenticationFilter.class)
            .authorizeHttpRequests(a -> a.anyRequest().hasAuthority("SCOPE_partner:read"))
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .csrf(c -> c.disable())
            .build();
}

Pitfalls

  • Registering the filter as a @Component too (Boot auto-registers it for every request).
  • Plaintext key storage.
  • Keys in query strings (logged by proxies).

Variations

  • OAuth2 client credentials with an IdP for larger partner ecosystems.
  • mTLS for high-assurance service-to-service auth.

Further reading: Spring Security: Architecture

Reference: https://docs.spring.io/spring-security/reference/servlet/architecture.html

More Spring Boot recipes