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>
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
- A state-changing request without CSRF is 403.
- Accessing the CSRF endpoint emits the configured Secure, non-HttpOnly CSRF cookie.
- Login with matching cookie/header creates only the bounded primitive authentication snapshot.
- The session cookie is host-only, Secure, HttpOnly, SameSite=Lax, path
/, and session-scoped. - After the first web context closes, a second independent context restores
/whoamifrom the same cookie through real Redis. - Logout force-revokes/tombstones the session; the old cookie is unauthenticated and a previously loaded stale session object cannot save over the tombstone.
- 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
DefaultSavedRequestframework 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.