Files
clean-architecture-backend-…/docs/superpowers/specs/2026-08-02-redis-session-http-boundary-design.md
T
DongHyeonkaandClaude Opus 5 5f10b791d3 chore: record pre-existing uncommitted repository state
Snapshot of the in-flight state that already existed, identically, in both
this worktree and the main checkout before this session began: the initial
HTTP Client platform implementation (previously untracked), the redis-lab
removal, and the JPA / object-storage / notification integration work.

Kept separate from this session's HTTP Client review response, which lands
in the following commit, so the two bodies of work stay reviewable apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 16:48:43 +09:00

4.5 KiB

Redis Session HTTP Boundary Design

Date: 2026-08-02 Status: approved by the user's instruction to apply the reviewed P1/P2 work sequentially Scope: composition of inbound browser-session security with the outbound versioned Redis session repository

Context

Inbound-web unit contracts prove CSRF, fixation, hardened cookie settings, and primitive security snapshot behavior with MockHttpSession/in-memory repositories. Cache-redis contracts prove the versioned session repository and Lua semantics against Redis. No test currently crosses the actual Spring Session filter, production SecurityFilterChain, real Redis, and a second application context.

Putting this test in inbound-web would require a forbidden dependency on the outbound Redis leaf. The composition root already depends on both leaves and owns the redisCompositionTest source set, so app-bootstrap is the correct boundary owner.

Decision

Add a tagged redis-session-http integration contract under app-bootstrap's existing redisCompositionTest source set. Ordinary redisCompositionTest excludes the tag. A new explicit redisSessionHttpIntegrationTest task includes only that tag, fails on no discovery or any skip, always reruns, pins UTC, and passes the checked-in Redis image registry path.

The task is deliberately not attached to ordinary local check, because it requires Docker. It is added to the existing release-blocking redis-standalone CI job, which is the Docker-capable Redis lane. Docker availability and container startup are attempted directly; no condition, assumption, or environment flag may convert absence into a skip.

The test loads redis.approved.image from src/gradle/redis-test-images.properties and rejects an unpinned reference. It creates an ephemeral CA/server certificate and a named, least-privilege ACL user, then connects with TLS, full hostname verification, and explicit CA trust. A runtime-generated Redis password and 32-byte HMAC are supplied through caller-owned versioned material; no secret value is checked in, passed on the Redis command line, or logged. Missing Docker or OpenSSL is a hard failure, not a skip.

The custom source set needs the Spring Session API at compile time. App-bootstrap therefore adds spring-session-core only to redisCompositionTestImplementation; the existing version is reused and the lockfile records the new custom compile configuration without changing a dependency version.

HTTP/Session Contract

  1. A state-changing request without CSRF is 403.
  2. Accessing the CSRF endpoint emits the configured Secure, non-HttpOnly CSRF cookie.
  3. Login with matching cookie/header creates only the bounded primitive authentication snapshot.
  4. The session cookie is host-only, Secure, HttpOnly, SameSite=Lax, path /, and session-scoped.
  5. After the first web context closes, a second independent context restores /whoami from the same cookie through real Redis.
  6. Logout force-revokes/tombstones the session; the old cookie is unauthenticated and a previously loaded stale session object cannot save over the tombstone.
  7. If Redis becomes unavailable during session lookup, the request fails closed before the protected controller and the surfaced exception graph contains only the repository's fixed availability message, not endpoint/password/session material.

The RED run exposed two production composition gaps which are part of this boundary:

  • the primitive security-context repository must wrap the response and persist before response commit, otherwise a successful response can commit before the first session is created;
  • the API security chain disables Spring Security's request cache, otherwise an unauthenticated request stores a DefaultSavedRequest framework graph that the primitive session codec correctly rejects.

Architecture

  • Inbound-web remains provider-neutral and has no outbound dependency.
  • Cache-redis keeps Redis keys, Lua, codec, HMAC, and tombstone policy private.
  • App-bootstrap assembles both adapters only for a cross-module composition contract.
  • No production dependency edge or dependency version changes; only a custom-test compile configuration is added to the existing lock entry.

Non-Goals

  • Redis Sentinel/Cluster sessions (production activation explicitly rejects them today);
  • browser-engine proof of SameSite behavior;
  • credential/certificate rotation qualification (the fixture still uses mandatory TLS, full hostname verification, explicit trust, and a named ACL user);
  • attaching Docker work to ordinary check;
  • staging, commit, amend, or push by an agent.