ADR-0005: La API no usa sesión HTTP
Estado: Aceptado · Fecha: 2026-09-24
Contexto
Con la política de sesión por defecto de Spring Security (IF_REQUIRED), el walking
skeleton se comportaba así:
GET /api/v1/sedes -> 401 Set-Cookie: JSESSIONID=D8D3DC...
GET /api/v1/sedes (con Basic)-> 200 Cookie: JSESSIONID=D8D3DC...
Una petición rechazada emitía sesión, y las siguientes la reenviaban. A partir de la primera llamada autenticada, una petición puede quedar autenticada por la cookie aunque su credencial sea incorrecta o falte.
Esto importa más aquí que en una API cualquiera. El LIS tendrá autorización por recurso —un cliente accede solo a sus propios resultados, un empleado a los de su sede— y toda lectura de un resultado sensible debe quedar auditada contra un actor concreto. Un test de autorización que pasa por una sesión heredada no prueba nada, y una auditoría que atribuye la lectura a la sesión anterior miente.
Decisión
SessionCreationPolicy.STATELESS y requestCache desactivado. Cada petición se autentica
por sí misma.
STATELESS por sí solo no basta, y esto costó descubrirlo. Esa política impide que Spring
Security guarde el contexto de seguridad en sesión, pero el RequestCache sigue guardando la
petición rechazada para reintentarla después del login, y al guardarla crea la sesión. El
JSESSIONID seguía apareciendo en el 401 con STATELESS ya puesto. En una API no hay login al
que volver, así que el cache sobra.
CSRF permanece desactivado, y solo es correcto mientras no haya autenticación por cookie. La línea que lo desactiva lleva el comentario que lo dice.
Cómo se verifica
Dos comprobaciones, porque una sola no bastaba:
- El test de integración mira
request.getSession(false). No vale comprobar que no hay cabeceraSet-Cookie: MockMvc no materializa la cookie de sesión, así que esa aserción pasa aunque el servidor real la emita. Pasó exactamente eso. wiki/http/curls_00_walking_skeleton.httpcompruebaSet-Cookiecontra el servidor real. Fue esta aserción la que detectó elRequestCache, con los tests de MockMvc en verde.
Consecuencias
- Una petición sin credencial válida se rechaza siempre, la anterior haya hecho lo que haya hecho. Los tests de autorización verifican lo que dicen verificar.
- No hay estado de sesión en el servidor: nada que replicar si algún día hay más de una instancia. − No hay “cerrar sesión” del lado del servidor. Con autenticación básica no hacía falta; con el modelo definitivo sí habrá que resolverlo.
Lo que esta decisión NO resuelve
DECISION REQUIRED antes de M8: el modelo de autenticación definitivo. Hoy hay un usuario de
desarrollo en memoria, que no es un modelo. Las opciones reales:
- Sesión con cookie para la interfaz interna (Thymeleaf, WebAuthn ya está en el
pom) y JWT/OAuth2 como resource server para integraciones (HL7, FHIR, analizadores). Dos cadenas de filtros, cada una con su política. - JWT para todo. Simple de describir, pero un token firmado no se puede revocar antes de su expiración sin mantener una lista de revocados, que es estado otra vez. En un sistema donde revocar el acceso de alguien tiene que ser inmediato —un empleado que sale, una credencial comprometida, una consulta a un resultado de VIH— esa propiedad no es menor.
Esta decisión es de producto y de normativa, no de código, y el marco la pone entre las que no
se delegan. STATELESS es compatible con las dos: no la anticipa.
Alternativas descartadas
Dejar IF_REQUIRED y confiar en que cada petición mande su credencial: funciona hasta que
alguien escribe un test de autorización que pasa por la cookie y se da por bueno.
Dos cadenas de filtros ya, una para /api/** y otra para la interfaz: la interfaz no
existe (D-03). Se añade cuando haya algo que proteger con sesión.