fix: agent auth, heartbeat, and SSE all break after server restart
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s

Three related issues caused by in-memory agent registry being empty
after server restart:

1. JwtAuthenticationFilter rejected valid agent JWTs if agent wasn't
   in registry — now authenticates any valid JWT regardless

2. Heartbeat returned 404 for unknown agents — now auto-registers
   the agent from JWT claims (subject, application)

3. SSE endpoint returned 404 — same auto-registration fix

JWT validation result is stored as a request attribute so downstream
controllers can extract the application claim for auto-registration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-03 10:41:23 +02:00
parent 0632f1c6a8
commit 2bc3efad7f
3 changed files with 46 additions and 25 deletions

View File

@@ -32,6 +32,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
private static final String BEARER_PREFIX = "Bearer ";
public static final String JWT_RESULT_ATTR = "cameleer.jwt.result";
private final JwtService jwtService;
private final AgentRegistryService agentRegistryService;
@@ -52,25 +53,17 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
JwtValidationResult result = jwtService.validateAccessToken(token);
String subject = result.subject();
if (subject.startsWith("user:")) {
// UI user token — authenticate with roles from JWT
List<GrantedAuthority> authorities = toAuthorities(result.roles());
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(subject, null, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
} else if (agentRegistryService.findById(subject) != null) {
// Agent token — use roles from JWT, default to AGENT if empty
List<String> roles = result.roles();
if (roles.isEmpty()) {
roles = List.of("AGENT");
}
List<GrantedAuthority> authorities = toAuthorities(roles);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(subject, null, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
} else {
log.debug("JWT valid but agent not found: {}", subject);
// Authenticate any valid JWT — agent registry is not authoritative
// (agents may hold valid tokens after server restart clears the in-memory registry)
List<String> roles = result.roles();
if (!subject.startsWith("user:") && roles.isEmpty()) {
roles = List.of("AGENT");
}
List<GrantedAuthority> authorities = toAuthorities(roles);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(subject, null, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
request.setAttribute(JWT_RESULT_ATTR, result);
} catch (Exception e) {
log.debug("JWT validation failed: {}", e.getMessage());
}