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>
80 lines
4.5 KiB
Markdown
80 lines
4.5 KiB
Markdown
# 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.
|