java 53 lines · 10 steps

Building an API key rotation filter in Spring

A once-per-request servlet filter that authenticates API keys, caches lookups, and signals clients when their key is due for rotation.

Explained by highlit
1@Component
2public class ApiKeyRotationFilter extends OncePerRequestFilter {
3 
4 private static final String API_KEY_HEADER = "X-Api-Key";
5 private static final Duration ROTATION_WINDOW = Duration.ofDays(7);
6 
7 private final ApiKeyRepository apiKeyRepository;
8 private final Cache keyCache;
9 
10 public ApiKeyRotationFilter(ApiKeyRepository apiKeyRepository, CacheManager cacheManager) {
11 this.apiKeyRepository = apiKeyRepository;
12 this.keyCache = cacheManager.getCache("apiKeys");
13 }
14 
15 @Override
16 protected boolean shouldNotFilter(HttpServletRequest request) {
17 return request.getServletPath().startsWith("/actuator");
18 }
19 
20 @Override
21 protected void doFilterInternal(HttpServletRequest request,
22 HttpServletResponse response,
23 FilterChain chain) throws ServletException, IOException {
24 
25 String presented = request.getHeader(API_KEY_HEADER);
26 if (!StringUtils.hasText(presented)) {
27 response.sendError(HttpStatus.UNAUTHORIZED.value(), "Missing API key");
28 return;
29 }
30 
31 ApiKey key = keyCache.get(presented, () -> apiKeyRepository.findByHashedValue(sha256(presented)).orElse(null));
32 
33 if (key == null || key.isRevoked()) {
34 response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid API key");
35 return;
36 }
37 
38 Instant age = key.getRotatedAt().plus(ROTATION_WINDOW);
39 if (Instant.now().isAfter(age)) {
40 response.setHeader("X-Api-Key-Rotation", "required");
41 response.setHeader("X-Api-Key-Expired-At", age.toString());
42 keyCache.evict(presented);
43 } else if (Instant.now().isAfter(age.minus(Duration.ofDays(2)))) {
44 response.setHeader("X-Api-Key-Rotation", "pending");
45 }
46 
47 chain.doFilter(request, response);
48 }
49 
50 private static String sha256(String value) {
51 return DigestUtils.sha256Hex(value.getBytes(StandardCharsets.UTF_8));
52 }
53}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A OncePerRequestFilter is the natural place to enforce cross-cutting authentication before requests reach controllers.
  2. 2Caching hashed-key lookups keeps per-request database hits low while still allowing eviction on revocation or expiry.
  3. 3Advisory response headers let a server nudge clients toward rotation without hard-failing valid-but-aging credentials.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building an API key rotation filter in Spring — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code