Skip to content

feat(security-006): split SecurityFilterChain into isolated API-key and JWT chains#12

Merged
hendrikebbers merged 4 commits intomainfrom
feat/006-split-security-filter-chains
Apr 28, 2026
Merged

feat(security-006): split SecurityFilterChain into isolated API-key and JWT chains#12
hendrikebbers merged 4 commits intomainfrom
feat/006-split-security-filter-chains

Conversation

@herbie-bot
Copy link
Copy Markdown
Collaborator

Summary

Replace the single SecurityFilterChain that mixed API-key and JWT authentication with two strictly isolated chains:

  • externalApiFilterChain (@Order(1), scoped to /api/external/**): API-key authentication only. Read-only HTTP methods (GET/HEAD/OPTIONS) are authorized declaratively at the chain level; all other methods are rejected with 403 Forbidden. JWT bearer tokens are not evaluated.
  • defaultFilterChain (@Order(2), all other paths): JWT/OAuth2 only. The X-API-Key header is silently ignored. Health and Swagger endpoints remain public.

The split makes the security model explicit, enables independent policies per chain, and prepares the library to host its own read-only controllers under /api/external/.

Spec

  • Spec folder: specs/006-split-security-filter-chains/
  • Design: specs/006-split-security-filter-chains/design.md
  • Behaviors: specs/006-split-security-filter-chains/behaviors.md

Changes

  • SecurityConfig now contributes two SecurityFilterChain beans plus an ApiKeyAuthenticationFilter @Bean constructed from ApiKeyDataService.
  • ApiKeyAuthenticationFilter no longer carries @Component (would auto-register on every chain) and no longer enforces HTTP-method authorization. It is a pure authentication adapter — chain-level authorizeHttpRequests enforces read-only access.
  • AuthService.getUserInformation() returns UserInformation("UNKNOWN", "UNKNOWN", null, null) for non-JWT principals (e.g., API-key requests) instead of throwing, so audit logging and similar callers work in API-key contexts.
  • package-info.java documentation updated for the two-chain model.
  • TODO.md records the follow-up to associate user identity with API keys.

Test coverage

  • Filter unit tests cover passthrough, invalid key (401), and valid key authentication for GET/HEAD/OPTIONS/POST (no longer rejected by the filter; chain handles 403).
  • AuthServiceTest covers JWT principal happy path, blank-subject error, and the new UNKNOWN user fallback for API-key and string principals.
  • SecurityConfigRoleTest updated for the new constructor signature (now takes ApiKeyDataService).
  • All 145 non-Testcontainers tests pass. Testcontainers-based integration tests fail in the local environment due to a Docker socket access issue that also affects main — unrelated to this PR.
  • Chain-level behaviors (POST denied, cross-chain rejection) rely on Spring Security's declarative authorization configuration; the project has no MockMvc test framework yet to verify them at integration level.

Closes #11

🤖 Generated with Claude Code

hendrikebbers and others added 4 commits April 28, 2026 13:20
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nd JWT chains

Replace the single SecurityFilterChain that mixed API-key and JWT
authentication with two strictly isolated chains:

- externalApiFilterChain (@order(1)) scoped to /api/external/**:
  API-key authentication only; GET/HEAD/OPTIONS allowed via declarative
  authorizeHttpRequests; all other methods denied at the chain level.
- defaultFilterChain (@order(2)) for everything else: JWT/OAuth2 only;
  X-API-Key header silently ignored. Health and Swagger remain public.

ApiKeyAuthenticationFilter is no longer @component (would auto-register
on every chain) and no longer enforces HTTP-method authorization — that
responsibility moved to the chain configuration. The filter is now
constructed via @bean and only added to chain 1 via addFilterBefore.

AuthService.getUserInformation() returns
UserInformation("UNKNOWN", "UNKNOWN", null, null) for non-JWT principals
instead of throwing, so audit logging and similar callers work in
API-key contexts. Tracked as a TODO to associate user identity with
API keys in a follow-up.

Includes tests covering the simplified filter behavior, the new UNKNOWN
user fallback in AuthService, and updated SecurityConfigRoleTest
constructor signature.

Spec: specs/006-split-security-filter-chains/

Closes #11

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hendrikebbers hendrikebbers merged commit 26db205 into main Apr 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Split SecurityFilterChain into isolated API-key and JWT chains

2 participants