diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b9db78..6bfa775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ include migration notes. ## Unreleased +- Consolidated public integration, compatibility, release, and async-security documentation; + removed the speculative roadmap and pinned upstream evidence to certified versions. +- Corrected HS256 guidance to distinguish the 32-character validation boundary from required key + entropy, and clarified that concurrent reset-token completion must be serialized by the + application or shared storage. + ## 7.2.0 - 2026-07-12 - Added stateless Spring MVC async and streaming support based on a request-attribute @@ -23,8 +29,8 @@ include migration notes. ## 7.0.0 - 2026-06-23 -- Certified Vigil for Spring Boot 4.1.x, Spring Framework 7.x, Spring Security - 7.1.x, Java 25, Gradle 9.1.x, and Jackson 3. +- Certified Vigil for Spring Boot 4.1.0, Spring Framework 7.0.8, Spring Security + 7.1.0, Java 25, Gradle 9.1.x, and Jackson 3. - Updated build tooling to Spring Boot 4.1.0, Java 25 toolchains, Gradle 9.1.0, JaCoCo 0.8.14, Checkstyle 13.6.0, and google-java-format 1.28.0. - Added starter auto-configuration compatibility tests that prove Vigil core diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a6d3aac..9cd592b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ Contributions are welcome when they keep Vigil small, explicit, tested, and secu ## Development Requirements - Java 25. -- Spring Boot 4.1.x compatibility. +- Use the exact platform versions in the [compatibility reference](docs/reference/compatibility.md). - Use the Gradle wrapper, not a system Gradle requirement. ## Quality Gates @@ -33,6 +33,8 @@ gradlew.bat build --no-daemon - Security-sensitive errors must not expose secrets, raw tokens, or parser internals. - Configuration should fail fast at startup when required security inputs are missing. - Architecture changes should update the relevant public docs in the same change. +- Completed user-visible work belongs in `CHANGELOG.md`; speculative features belong in GitHub + Issues or Projects, not the active documentation tree. ## Scope Rules diff --git a/README.md b/README.md index 6ac2cb4..2a98557 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,15 @@ JWT authentication infrastructure for Spring Boot applications. [![Maven Central](https://img.shields.io/maven-central/v/io.github.sequelcore/vigil-spring-boot-starter.svg)](https://central.sonatype.com/artifact/io.github.sequelcore/vigil-spring-boot-starter) -Vigil provides JWT lifecycle, request authentication, cookie helpers, tenant consistency, revocation, reset tokens, and reusable step-up credential verification. Applications retain ownership of users, login routes, credentials, recovery delivery, and business authorization. +Vigil provides JWT lifecycle, request authentication, cookie helpers, tenant consistency, +revocation, reset tokens, and reusable step-up credential verification. Applications retain +ownership of users and business authorization; the complete boundary is documented in +[system boundaries](docs/architecture/system-boundaries.md). ## Compatibility -Vigil `7.2.x` is certified with Java 25, Spring Boot 4.1.0, Spring Framework 7.0.8, -Spring Security 7.1.0, Gradle 9.6.x, and Jackson 3. Later dependency patches are not claimed as -certified until they pass Vigil's complete gate. Vigil `6.0.x` was the final Java 21 / Spring Boot -3.5 line. +Vigil `7.2.0` is certified with Java 25 and Spring Boot 4.1.0. See the +[compatibility reference](docs/reference/compatibility.md) for the complete tested combination. `7.2.0` is the current release line. Public consumers should pin an exact version and review the release notes before upgrading. @@ -35,26 +36,17 @@ vigil: audience: my-api ``` -```java -http.addFilterBefore(vigilAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); -``` - -The application must still configure route authorization. `ignored-paths` skips Vigil processing; it does not grant anonymous access. +Follow the complete [authentication guide](docs/guides/authentication.md) to install the filter, +request-scoped security repository, stateless session policy, and application authorization rules. +`ignored-paths` skips Vigil processing; it does not grant anonymous access. ## Documentation -Start at the [documentation index](docs/README.md). +Start at the [documentation index](docs/README.md). The primary integration references are: - [Authentication guide](docs/guides/authentication.md) - [Async and streaming security](docs/guides/async-streaming-security.md) - [Configuration reference](docs/reference/configuration.md) -- [System boundaries](docs/architecture/system-boundaries.md) -- [Security model](docs/security/security-model.md) -- [Step-up authorization](docs/security/step-up-authorization.md) -- [Deployment and operations](docs/operations/deployment.md) -- [Java API contract](docs/api/java-api.md) -- [Testing and verification](docs/development/testing.md) -- [Release policy](docs/releases/release-policy.md) ## Verification diff --git a/SECURITY.md b/SECURITY.md index 311f8a8..df0f6c0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,9 @@ ## Supported Versions -Until a stable public support policy is adopted, only the current `main` branch receives security fixes. +Vigil 7.2.x and the current `main` branch receive security fixes. Older release lines are +unsupported unless a security advisory states otherwise. See the +[compatibility reference](docs/reference/compatibility.md). ## Reporting A Vulnerability diff --git a/docs/README.md b/docs/README.md index 3c6b840..2ebd94d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,13 +1,14 @@ # Vigil documentation -Vigil is a Spring Boot starter for application-owned JWT authentication. It provides token lifecycle, request authentication, cookie helpers, tenant consistency, revocation, reset tokens, and step-up credential verification. It does not own users, product authorization, or identity-delivery flows. +Vigil is a Spring Boot starter for application-owned JWT authentication. Read +[system boundaries](architecture/system-boundaries.md) before integrating it into an application. ## Start here -- [Async and streaming security](guides/async-streaming-security.md) — preserve stateless authentication across MVC redispatches. - - [Authentication guide](guides/authentication.md) — configure JWTs, cookies, Spring Security, tenants, and reset tokens. +- [Async and streaming security](guides/async-streaming-security.md) — preserve stateless authentication across MVC redispatches. - [Configuration reference](reference/configuration.md) — every `vigil.*` property and its defaults. +- [Compatibility reference](reference/compatibility.md) — certified platform and dependency versions. - [System boundaries](architecture/system-boundaries.md) — understand ownership and extension points before integrating. - [Java API contract](api/java-api.md) — public services, SPIs, and compatibility expectations. - [Step-up proof decision](adr/0001-step-up-opaque-one-time-proofs.md) — why approvals are opaque and server-consumed. @@ -22,6 +23,5 @@ Vigil is a Spring Boot starter for application-owned JWT authentication. It prov - [Testing and verification](development/testing.md) — local gates and integration coverage. - [Release policy](releases/release-policy.md) — compatibility, versioning, and publication controls. -- [Roadmap](roadmap.md) — supported scope and deliberately deferred work. The root [README](../README.md) is the concise package landing page. This index is the canonical navigation surface for repository documentation. diff --git a/docs/api/java-api.md b/docs/api/java-api.md index 0de84c6..c73ffeb 100644 --- a/docs/api/java-api.md +++ b/docs/api/java-api.md @@ -8,7 +8,7 @@ This guide names Vigil's public integration contracts. It is not generated Javad | --- | --- | | `VigilAuthService` | Validate application credentials before issuing, refreshing, or revoking tokens. | | `VigilTokenService` | Use only when a lower-level token integration is genuinely needed. | -| `VigilResetTokenService` | Deliver the reset token and update the user's password after successful consumption. | +| `VigilResetTokenService` | Deliver the token, serialize concurrent completion in application/shared storage, and update the password only after successful consumption. See the [security model](../security/security-model.md). | | `VigilPasswordService` | Store its returned hash; own password policy and user persistence. | | `VigilTenantContext` | Read the validated request tenant. | diff --git a/docs/research/async-streaming-security-sources.md b/docs/architecture/async-streaming-evidence.md similarity index 75% rename from docs/research/async-streaming-security-sources.md rename to docs/architecture/async-streaming-evidence.md index 454e5c2..b2e5cf2 100644 --- a/docs/research/async-streaming-security-sources.md +++ b/docs/architecture/async-streaming-evidence.md @@ -1,4 +1,4 @@ -# Async and streaming security research +# Async and streaming security evidence Research cutoff: 2026-07-12. Primary specifications, official documentation, upstream source, tests, and issue discussions take precedence over secondary guidance. @@ -10,26 +10,26 @@ tests, and issue discussions take precedence over secondary guidance. | [Servlet 6.1 specification](https://jakarta.ee/specifications/servlet/6.1/jakarta-servlet-spec-6.1.pdf), [AsyncContext](https://jakarta.ee/specifications/servlet/6.1/apidocs/jakarta.servlet/jakarta/servlet/asynccontext), [AsyncListener](https://jakarta.ee/specifications/servlet/6.1/apidocs/jakarta.servlet/jakarta/servlet/asynclistener) | `startAsync` lets the filter/servlet chain return while the same response remains open. Async error and timeout notifications belong to the container lifecycle; a new async cycle requires listener registration again. | Preserve security evidence on the servlet request. Do not replay credentials, manually complete an emitter after failed send, or equate return from the initial chain with request completion. | | [DispatcherType](https://jakarta.ee/specifications/servlet/6.1/apidocs/jakarta.servlet/jakarta/servlet/dispatchertype), [ServletRequest.getDispatcherType](https://jakarta.ee/specifications/servlet/6.1/apidocs/jakarta.servlet/jakarta/servlet/servletrequest#getDispatcherType()) | `REQUEST`, `ASYNC`, and `ERROR` are distinct filter-chain invocations. `ASYNC` is produced by `AsyncContext.dispatch`; `ERROR` is container error handling. | Authenticate credentials only during the initial request. Continue authorization on redispatch using saved request evidence. A dispatch without that evidence fails closed. | | [ServletResponse](https://jakarta.ee/specifications/servlet/6.1/apidocs/jakarta.servlet/jakarta/servlet/servletresponse) | Flushing commits status and headers; reset after commit is illegal and later status/header changes cannot repair the response. | Prevent the secondary authorization failure. Do not attempt to render a new 401/403 over committed SSE output. | -| [Spring MVC async processing](https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-ann-async.html), [ResponseBodyEmitter](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.html), [SseEmitter](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html) | `DeferredResult` and streaming return types finish through an `ASYNC` dispatch. An emitter `IOException` caused by client disconnect must be left to the container and MVC, which perform error notification, final dispatch, exception resolution, and completion. | Broken pipe remains an expected network event. Vigil preserves authentication for the final dispatch instead of catching or suppressing the `IOException`. | -| [OncePerRequestFilter](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/OncePerRequestFilter.html) | Async and error dispatch participation is opt-in; both can run on different threads. | Vigil keeps the authentication filter out of redispatch. JWT validation, blacklist lookup, tenant extraction, hooks, and context populators execute once. | +| [Spring MVC async processing](https://docs.spring.io/spring-framework/reference/7.0/web/webmvc/mvc-ann-async.html), [ResponseBodyEmitter 7.0.8](https://docs.spring.io/spring-framework/docs/7.0.8/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitter.html), [SseEmitter 7.0.8](https://docs.spring.io/spring-framework/docs/7.0.8/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html) | `DeferredResult` and streaming return types finish through an `ASYNC` dispatch. An emitter `IOException` caused by client disconnect must be left to the container and MVC, which perform error notification, final dispatch, exception resolution, and completion. | Broken pipe remains an expected network event. Vigil preserves authentication for the final dispatch instead of catching or suppressing the `IOException`. | +| [OncePerRequestFilter 7.0.8](https://docs.spring.io/spring-framework/docs/7.0.8/javadoc-api/org/springframework/web/filter/OncePerRequestFilter.html) | Async and error dispatch participation is opt-in; both can run on different threads. | Vigil keeps the authentication filter out of redispatch. JWT validation, blacklist lookup, tenant extraction, hooks, and context populators execute once. | | [Spring Security context persistence](https://docs.spring.io/spring-security/reference/7.0/servlet/authentication/persistence.html), [RequestAttributeSecurityContextRepository API](https://docs.spring.io/spring-security/reference/7.1/api/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepository.html) | Custom authentication must explicitly save. The request-attribute repository restores one request across dispatch types and never persists to later requests. `SecurityContextHolderFilter` loads and clears the holder. | Save the authenticated context explicitly to a request-attribute repository configured in the application chain. This is stateless and creates no `HttpSession`. | | [Servlet authorization](https://docs.spring.io/spring-security/reference/7.0/servlet/authorization/authorize-http-requests.html), [Servlet async integration](https://docs.spring.io/spring-security/reference/7.0/servlet/integrations/servlet-api.html) | Authorization applies to dispatcher types by default. Async task propagation and redispatch persistence are related but distinct mechanisms. | Vigil does not install dispatcher `permitAll` rules. The application continues to own HTTP authorization, including any narrow error-rendering exception. | ## Upstream implementation and test evidence -- Spring Framework 7.0.x: - - [`ResponseBodyEmitterReturnValueHandler`](https://github.com/spring-projects/spring-framework/blob/7.0.x/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java) - - [`DeferredResultMethodReturnValueHandler`](https://github.com/spring-projects/spring-framework/blob/7.0.x/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java) - - [`ResponseBodyEmitterReturnValueHandlerTests`](https://github.com/spring-projects/spring-framework/blob/7.0.x/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandlerTests.java) - - [`StreamingResponseBodyReturnValueHandlerTests`](https://github.com/spring-projects/spring-framework/blob/7.0.x/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandlerTests.java) -- Spring Security 7.0.x: - - [`RequestAttributeSecurityContextRepository`](https://github.com/spring-projects/spring-security/blob/7.0.x/web/src/main/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepository.java) and [tests](https://github.com/spring-projects/spring-security/blob/7.0.x/web/src/test/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepositoryTests.java) - - [`SecurityContextHolderFilter`](https://github.com/spring-projects/spring-security/blob/7.0.x/web/src/main/java/org/springframework/security/web/context/SecurityContextHolderFilter.java) and [tests](https://github.com/spring-projects/spring-security/blob/7.0.x/web/src/test/java/org/springframework/security/web/context/SecurityContextHolderFilterTests.java) - - [`AuthorizationFilter`](https://github.com/spring-projects/spring-security/blob/7.0.x/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java) and [tests](https://github.com/spring-projects/spring-security/blob/7.0.x/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java) - - [`ExceptionTranslationFilter`](https://github.com/spring-projects/spring-security/blob/7.0.x/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java) and [tests](https://github.com/spring-projects/spring-security/blob/7.0.x/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java) -- Spring Boot 4.1.x: - - [`SecurityFilterAutoConfiguration`](https://github.com/spring-projects/spring-boot/blob/main/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/SecurityFilterAutoConfiguration.java) - - [`SecurityFilterAutoConfigurationTests`](https://github.com/spring-projects/spring-boot/blob/main/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/web/servlet/SecurityFilterAutoConfigurationTests.java) +- Spring Framework 7.0.8: + - [`ResponseBodyEmitterReturnValueHandler`](https://github.com/spring-projects/spring-framework/blob/v7.0.8/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java) + - [`DeferredResultMethodReturnValueHandler`](https://github.com/spring-projects/spring-framework/blob/v7.0.8/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java) + - [`ResponseBodyEmitterReturnValueHandlerTests`](https://github.com/spring-projects/spring-framework/blob/v7.0.8/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandlerTests.java) + - [`StreamingResponseBodyReturnValueHandlerTests`](https://github.com/spring-projects/spring-framework/blob/v7.0.8/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandlerTests.java) +- Spring Security 7.1.0: + - [`RequestAttributeSecurityContextRepository`](https://github.com/spring-projects/spring-security/blob/7.1.0/web/src/main/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepository.java) and [tests](https://github.com/spring-projects/spring-security/blob/7.1.0/web/src/test/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepositoryTests.java) + - [`SecurityContextHolderFilter`](https://github.com/spring-projects/spring-security/blob/7.1.0/web/src/main/java/org/springframework/security/web/context/SecurityContextHolderFilter.java) and [tests](https://github.com/spring-projects/spring-security/blob/7.1.0/web/src/test/java/org/springframework/security/web/context/SecurityContextHolderFilterTests.java) + - [`AuthorizationFilter`](https://github.com/spring-projects/spring-security/blob/7.1.0/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java) and [tests](https://github.com/spring-projects/spring-security/blob/7.1.0/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java) + - [`ExceptionTranslationFilter`](https://github.com/spring-projects/spring-security/blob/7.1.0/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java) and [tests](https://github.com/spring-projects/spring-security/blob/7.1.0/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java) +- Spring Boot 4.1.0: + - [`SecurityFilterAutoConfiguration`](https://github.com/spring-projects/spring-boot/blob/v4.1.0/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/SecurityFilterAutoConfiguration.java) + - [`SecurityFilterAutoConfigurationTests`](https://github.com/spring-projects/spring-boot/blob/v4.1.0/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/web/servlet/SecurityFilterAutoConfigurationTests.java) The local executable baseline resolves Spring Boot 4.1.0, Spring Framework MVC 7.0.8, Spring Security Web 7.1.0, Tomcat 11.0.22, and Servlet 6.1. Vigil does not claim unexecuted version ranges. diff --git a/docs/architecture/system-boundaries.md b/docs/architecture/system-boundaries.md index c6601a3..bc66ef9 100644 --- a/docs/architecture/system-boundaries.md +++ b/docs/architecture/system-boundaries.md @@ -11,7 +11,7 @@ Vigil is authentication infrastructure for Spring Boot applications. It standard | JWT generation, validation, signing-key handling, JWKS, refresh rotation, and revocation | User persistence, lookup, registration, and credential enrollment UI | | Cookie and bearer-token extraction | Login, logout, reset, and recovery routes | | Request authentication filter and tenant consistency | `SecurityFilterChain` authorization rules and tenant membership | -| Password hashing helpers and reset-token lifecycle | Password policy, recovery delivery, and account recovery UX | +| Password hashing helpers and reset-token validation/invalidation | Password policy, recovery delivery, concurrent reset serialization, and account recovery UX | | Step-up challenge/proof lifecycle and credential-verifier SPI | Business approval policy, roles, limits, segregation of duties, and audit decision | Vigil is not an OAuth authorization server, OpenID Connect provider, hosted identity platform, user-management system, or business authorization engine. diff --git a/docs/development/testing.md b/docs/development/testing.md index 0d129e2..46a9395 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -31,8 +31,7 @@ Async security changes additionally require the real filter chain tests in RST followed by `IOException`, final `ASYNC` processing, `ERROR` dispatch, callback cleanup, and the absence of a secondary authentication entry point or access-denied response. -The certified dependency combination is resolved by the Spring Boot BOM in `build.gradle.kts`. -Documentation must name the exact versions exercised by the full gate; an untested `4.1.x`, `7.x`, -or `7.1.x` range is not a supported compatibility claim. +The certified dependency combination is resolved by the Spring Boot BOM in `build.gradle.kts` and +recorded in the [compatibility reference](../reference/compatibility.md). Use application integration tests for application-owned routes and user persistence. Vigil tests do not replace product authorization or user-lifecycle tests. diff --git a/docs/guides/async-streaming-security.md b/docs/guides/async-streaming-security.md index 17a8327..3139bd3 100644 --- a/docs/guides/async-streaming-security.md +++ b/docs/guides/async-streaming-security.md @@ -4,30 +4,12 @@ Vigil preserves an authenticated Spring Security context for the lifetime of one including its `ASYNC` and `ERROR` redispatches. It does not create an `HttpSession`, revalidate a JWT during redispatch, or weaken the application's authorization rules. -## Secure stateless configuration +## Required security-chain configuration -Use Vigil's request-scoped repository in the application's filter chain. Keep authorization rules -application-owned and continue authorizing every dispatcher type. - -```java -@Bean -SecurityFilterChain securityFilterChain( - HttpSecurity http, - VigilAuthenticationFilter vigilAuthenticationFilter) throws Exception { - var requestSecurityContextRepository = new RequestAttributeSecurityContextRepository(); - vigilAuthenticationFilter.setSecurityContextRepository(requestSecurityContextRepository); - return http - .sessionManagement(session -> - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .securityContext(context -> - context.securityContextRepository(requestSecurityContextRepository)) - .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/auth/**").permitAll() - .anyRequest().authenticated()) - .addFilterBefore(vigilAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .build(); -} -``` +Use the complete stateless `SecurityFilterChain` in the +[authentication guide](authentication.md#2-install-the-authentication-filter). The filter and +Spring Security must use the same `RequestAttributeSecurityContextRepository`. Keep authorization +rules application-owned and continue authorizing every dispatcher type. Vigil validates credentials and runs authentication hooks and context populators only on the initial `REQUEST`. After successful authentication it saves the `SecurityContext` in a @@ -66,18 +48,12 @@ not arbitrary application `ThreadLocal` values. ## Source-backed decisions The complete auditable research record, including upstream source/tests, issue evidence, and the -alternatives matrix, is in [async and streaming security research](../research/async-streaming-security-sources.md). - -- [Jakarta Servlet 6.1](https://jakarta.ee/specifications/servlet/6.1/jakarta-servlet-spec-6.1.pdf): `ASYNC` is a dispatch of the same request, supporting request attributes rather than token replay or sessions. -- [Spring Framework async MVC](https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-ann-async.html): documents final dispatch and emitter `IOException` handling. -- [Spring Security context persistence](https://docs.spring.io/spring-security/reference/7.0/servlet/authentication/persistence.html): defines request-attribute persistence and explicit saving for custom authentication. -- [Spring Security authorization](https://docs.spring.io/spring-security/reference/7.0/servlet/authorization/authorize-http-requests.html): dispatcher authorization remains application policy. -- [Spring Security issue 12758](https://github.com/spring-projects/spring-security/issues/12758): maintainers prescribe this repository for the equivalent JWT and `StreamingResponseBody` failure. -- [Spring Framework issue 33439](https://github.com/spring-projects/spring-framework/issues/33439): disconnect timing is network/container dependent. +alternatives matrix, is in [async and streaming security evidence](../architecture/async-streaming-evidence.md). ## Migration Synchronous integrations keep their behavior. Async applications must install a -`RequestAttributeSecurityContextRepository` in `HttpSecurity` as shown above. Remove broad +`RequestAttributeSecurityContextRepository` in `HttpSecurity` as shown in the authentication +guide. Remove broad `dispatcherTypeMatchers(ASYNC, ERROR).permitAll()` workarounds after verifying application error routes. No token, cookie, route, or authorization contract changes are required. diff --git a/docs/guides/authentication.md b/docs/guides/authentication.md index a6815d5..d6d963a 100644 --- a/docs/guides/authentication.md +++ b/docs/guides/authentication.md @@ -28,6 +28,8 @@ SecurityFilterChain securityFilterChain( var requestSecurityContextRepository = new RequestAttributeSecurityContextRepository(); vigilAuthenticationFilter.setSecurityContextRepository(requestSecurityContextRepository); return http + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .securityContext(context -> context.securityContextRepository(requestSecurityContextRepository)) .authorizeHttpRequests(authorize -> authorize @@ -38,11 +40,14 @@ SecurityFilterChain securityFilterChain( } ``` -The request-scoped repository is required for MVC async and streaming return types. See +The example is stateless: the repository is request-scoped and does not create an `HttpSession`. +It is required for MVC async and streaming return types. See [async and streaming security](async-streaming-security.md) for the stateless lifecycle and dispatcher authorization model. -`ignored-paths` bypasses Vigil entirely. `public-paths` permits an anonymous request while making a valid existing authentication available to the application. Neither setting replaces `authorizeHttpRequests`. +`ignored-paths` bypasses Vigil processing. `public-paths` lets Vigil continue without a credential +while still authenticating one when present. Neither setting grants access: the application must +configure matching `permitAll` rules when anonymous access is intended. ## 3. Issue tokens after application credential validation diff --git a/docs/operations/deployment.md b/docs/operations/deployment.md index 0b99179..f8f0999 100644 --- a/docs/operations/deployment.md +++ b/docs/operations/deployment.md @@ -19,10 +19,13 @@ The built-in Caffeine implementations are single-node defaults. | Feature | Production cluster requirement | | --- | --- | | blacklist and refresh rotation | provide a shared `VigilBlacklistBackend` | +| password reset | provide a shared blacklist backend and serialize concurrent completion for the same reset token | | step-up authorization | provide a shared `StepUpStore` with atomic challenge/proof consumption | | failed-attempt protection | place a shared rate limiter or equivalent protection in front of the application when node-local lockout is insufficient | -Do not use eventually consistent state for one-time proof consumption. A proof must be consumed at most once across all nodes. +Do not use eventually consistent state for one-time step-up proof consumption. A proof must be +consumed at most once across all nodes. Reset-token validation and invalidation are separate +operations; the application must prevent concurrent password resets with the same token. ## Operational checks diff --git a/docs/reference/compatibility.md b/docs/reference/compatibility.md new file mode 100644 index 0000000..72aad96 --- /dev/null +++ b/docs/reference/compatibility.md @@ -0,0 +1,35 @@ +# Compatibility reference + +Vigil certifies the platform components that define its runtime contract. A version range is not +supported until that combination passes the complete repository gate. + +## Current line + +| Component | Certified version | +| --- | --- | +| Vigil | 7.2.0 | +| Java | 25 | +| Spring Boot BOM | 4.1.0 | +| Spring Framework MVC | 7.0.8 | +| Spring Security Web | 7.1.0 | +| Jackson Databind | 3.1.4, through `tools.jackson` packages | +| Gradle wrapper | 9.6.1 | +| Embedded test container | Tomcat 11.0.22, implementing Servlet 6.1 | + +Vigil 6.0.x was the final Java 21 and Spring Boot 3.5 line. It is not a compatibility target for +Spring Boot 4 applications. The Jackson 2 to Jackson 3 constructor change was released in Vigil +7.0.0 and is recorded in the [changelog](../../CHANGELOG.md#700---2026-06-23). + +## Certification rule + +The Spring Boot BOM selects Framework, Security, Jackson, validation, and container dependencies. +Do not override individual managed versions and describe the result as certified. To certify a new +combination, update the BOM, run the focused integration tests, then run: + +```bat +gradlew.bat clean check --no-daemon +gradlew.bat qualityCheck build --no-daemon +``` + +Async certification includes a real embedded-Tomcat client disconnect, final `ASYNC` processing, +container `ERROR` dispatch, concurrency isolation, and absence of `HttpSession`. diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 94e0d07..6e50c11 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -31,6 +31,10 @@ vigil: `rsa-private-key`, `rsa-public-key`, and `rsa-public-keys` accept inline PEM, `classpath:` or `file:` locations. RS256 automatically registers `/.well-known/jwks.json`. +The HS256 length check counts characters; it does not measure entropy. Generate random key material +with at least 256 bits of entropy and encode it without truncation. Do not use a human password or +repeated text merely because it reaches 32 characters. + ## Optional configuration ```yaml @@ -90,7 +94,26 @@ vigil: ## Validation and deployment notes +Spring Boot duration syntax is accepted for duration properties, for example `30s`, `15m`, and +`7d`. Lists and maps use standard YAML binding. + +| Property | Validation or normalization | +| --- | --- | +| `jwt.secret` | Required for HS256; at least 32 characters | +| `jwt.clock-skew` | Between `0s` and `5m` | +| `jwt.rsa-private-key`, `jwt.rsa-public-key` | Both required for RS256 | +| `password.strength` | BCrypt cost `4`–`31`; invalid values normalize to `12` | +| `blacklist.max-size` | Non-positive values normalize to `10000` | +| `blacklist.grace-period` | Values above `60s` normalize to `60s` | +| `protection.max-attempts`, `protection.max-size` | Non-positive values normalize to their defaults | +| `step-up.challenge-ttl`, `step-up.proof-ttl` | Non-positive values normalize to their defaults | +| `step-up.pin.min-length` | Values below `4` normalize to `6` | +| `step-up.pin.max-length` | Must be at least `min-length` and at most `128`; otherwise normalizes to `12` | +| `step-up.pin.bcrypt-strength` | BCrypt cost `4`–`31`; invalid values normalize to `12` | + - Invalid JWT signing configuration fails at application startup. - `secure: true` requires HTTPS for browser clients. - The built-in blacklist, protection, and step-up stores are node-local. See [deployment and operations](../operations/deployment.md) before running more than one instance. -- Configuration only controls authentication infrastructure. Route authorization, user lifecycle, credential lookup, and business approval policy remain application-owned. +- `public-paths` changes Vigil credential processing only. The application must configure its own + anonymous authorization rules. +- Ownership details are canonical in [system boundaries](../architecture/system-boundaries.md). diff --git a/docs/releases/release-policy.md b/docs/releases/release-policy.md index 9014991..19c6d73 100644 --- a/docs/releases/release-policy.md +++ b/docs/releases/release-policy.md @@ -1,7 +1,8 @@ -# Release Policy +# Release policy -Vigil is published as a Spring Boot starter for JWT authentication. Version -`7.2.0` is the current release line and Spring Boot 4.1 certification baseline. +Vigil is published as a Spring Boot starter for JWT authentication. The +[compatibility reference](../reference/compatibility.md) records the current release and certified +platform combination. Vigil is used by Sequel applications, but public consumers should pin exact versions, read release notes, and review migration notes before upgrades. @@ -17,7 +18,7 @@ Vigil follows semantic versioning for public releases: Every breaking change must include migration notes that identify the affected package, public type, method, property, replacement path, and automation status. -## Public Compatibility Surface +## Public compatibility surface Vigil treats these as public compatibility surfaces: @@ -32,33 +33,10 @@ Vigil treats these as public compatibility surfaces: Implementation details that are not documented as public contracts may change between releases when tests and public behavior remain stable. -Current tested compatibility envelope: +Untested combinations must not be described as supported in release notes, the README, Maven +metadata, or examples. Do not add compatibility shims for unsupported platform lines. -- Java 25; -- Spring Boot 4.1.0; -- Spring Framework 7.0.8 through the Spring Boot 4.1.0 BOM; -- Spring Security 7.1.0 through the Spring Boot 4.1.0 BOM; -- Jackson 3 through `tools.jackson` packages; -- Gradle 9.6.x wrapper; -- HS256 with a configured 256-bit minimum secret; -- RS256 with configured PEM private/public keys and JWKS publication. - -Vigil `7.2.x` is the active supported platform line. Vigil `6.0.x` was the -final Spring Boot 3.5.x / Java 21 line and is not supported for Spring Boot -4.1 consumers. Do not add compatibility shims between the two lines; Boot 4 -changes the default JSON stack to Jackson 3 and modularizes several Boot -support packages. - -The `7.0.0` line intentionally changes -`VigilAuthenticationEntryPoint(String, ObjectMapper)` from Jackson 2 -`com.fasterxml.jackson.databind.ObjectMapper` to Jackson 3 -`tools.jackson.databind.ObjectMapper`. This is a public constructor type change -and therefore requires a major release. - -Untested compatibility must not be described as supported in release notes, -README, Maven metadata, or examples. - -## Artifact Policy +## Artifact policy Java artifacts use group `io.github.sequelcore`. @@ -69,7 +47,7 @@ Public artifact: Publish only the starter artifact to Maven Central. Do not publish local test fixtures, generated reports, examples, or build output. -## Release Readiness Checklist +## Release readiness checklist Before a public release: @@ -77,7 +55,7 @@ Before a public release: 2. The relevant guide reflects current behavior and `docs/reference/configuration.md` reflects every configuration change. 3. `CHANGELOG.md` contains user-visible changes. 4. Public API or configuration changes have migration notes. -5. Architecture changes are documented in the relevant public guide or roadmap. +5. Architecture changes are documented in the relevant public guide, ADR, or architecture record. 6. Security changes are covered by focused tests. 7. `gradlew.bat clean check --no-daemon` passes locally. 8. `gradlew.bat qualityCheck --no-daemon` passes locally. @@ -87,7 +65,7 @@ Before a public release: any upload step. 11. Release notes are drafted from verified repository changes. -## Dry-Run Commands +## Dry-run commands Quality gate: @@ -102,7 +80,7 @@ Maven local publication: gradlew.bat publishToMavenLocal --no-daemon ``` -## Publication Policy +## Publication policy Release automation is manual and guarded. The default workflow operation is `validate`, which checks the release candidate without fetching publisher @@ -111,6 +89,7 @@ credentials or uploading artifacts. Maven Central upload requires a separate Publishing requires: +- the workflow dispatch ref equal to `v`; - `release_ref` equal to `v`; - `confirmation` equal to `publish `; - the protected GitHub `release` environment; diff --git a/docs/roadmap.md b/docs/roadmap.md deleted file mode 100644 index b299e6b..0000000 --- a/docs/roadmap.md +++ /dev/null @@ -1,52 +0,0 @@ -# Roadmap - -Vigil handles token lifecycle, not user lifecycle. The roadmap should keep that -boundary intact. - -Completed work is tracked in `CHANGELOG.md`. This document only describes the -current supported scope and future candidates. - -## Current Scope - -- Java 25 and Spring Boot 4.1.x. -- HS256 JWT signing with minimum 256-bit secret validation. -- RS256 JWT signing with JWKS publication. -- RS256 public-key rotation through verification-only previous keys. -- Access and refresh token generation, validation, and refresh rotation. -- HTTP-only cookie profiles for browser clients. -- Bearer token flows for native clients and APIs. -- Token and subject invalidation through `VigilBlacklistService`. -- Application-provided shared `VigilBlacklistBackend` beans for multi-instance - revocation storage. -- Spring Security authentication filter integration. -- Tenant context validation. -- Guest session hooks. -- BCrypt password helpers and single-use reset tokens. -- Step-up authorization with generic credential verifiers, one-time proofs, and personal PIN support. - -Vigil does not claim OAuth authorization-server support, OIDC provider support, -hosted identity management, user storage, credential validation ownership, MFA -or delivery-channel ownership, or application authorization policy. - -## Candidate Work - -Future work must stay within Vigil's token lifecycle boundary: - -- packaged Redis blacklist backend adapter as an optional integration module; -- ES256 signing through the existing `TokenSigner` extension point; -- DPoP proof-of-possession support if the API boundary can stay small; -- additional reset-token or session hardening driven by real application use. - -Do not add these before a release unless they are release-blocking. New features -need tests and docs when they change architecture or public contracts. - -## Release Discipline - -Every completed roadmap slice must update: - -- `CHANGELOG.md` for user-visible changes; -- `docs/guides/authentication.md` when the application integration path changes; -- `docs/reference/configuration.md` when a `vigil.*` property or default changes; -- `docs/security/step-up-authorization.md` when the step-up boundary changes; -- `docs/releases/release-policy.md` when release policy changes; -- the relevant public guide when the architecture boundary changes. diff --git a/docs/security/security-model.md b/docs/security/security-model.md index 5199d95..291f243 100644 --- a/docs/security/security-model.md +++ b/docs/security/security-model.md @@ -3,7 +3,8 @@ ## Security invariants - Validate signed JWTs, configured issuer, and configured audience before trusting claims. -- Keep HS256 secrets at least 256 bits and limit signing authority to trusted services. +- Keep HS256 secrets at least 32 characters, generated from at least 256 bits of random entropy, + and limit signing authority to trusted services. - Prefer RS256 and JWKS where multiple services verify tokens but should not sign them. - Use HTTP-only, Secure cookies over HTTPS for browser flows. - Keep access tokens short-lived; use bounded clock skew only for known clock drift. @@ -12,7 +13,15 @@ ## Trust assumptions -Vigil trusts application-supplied credential validation, user lookup, and shared state adapters. A custom `VigilBlacklistBackend` or `StepUpStore` is security-sensitive infrastructure: it must preserve TTLs and perform the documented read/consume operations atomically across application nodes. +Vigil trusts application-supplied credential validation, user lookup, and shared state adapters. A +custom `VigilBlacklistBackend` must preserve TTLs and make revocation state visible across nodes. A +custom `StepUpStore` must additionally consume proofs atomically. + +`VigilResetTokenService.validateAndConsume` invalidates a reset token after validation, but the current +blacklist contract does not provide an atomic consume operation. Applications that can process the +same reset token concurrently must serialize reset completion or enforce uniqueness in shared +storage. Do not treat reset tokens as equivalent to step-up proofs, whose store contract requires +atomic consumption. ## Step-up controls diff --git a/src/main/java/io/github/sequelcore/vigil/auth/VigilResetTokenService.java b/src/main/java/io/github/sequelcore/vigil/auth/VigilResetTokenService.java index 1d8402a..f29f2ba 100644 --- a/src/main/java/io/github/sequelcore/vigil/auth/VigilResetTokenService.java +++ b/src/main/java/io/github/sequelcore/vigil/auth/VigilResetTokenService.java @@ -19,7 +19,7 @@ *
    *
  • Cryptographically signed (tamper-proof) *
  • Time-limited (configurable TTL) - *
  • Single-use (automatically blacklisted after consumption) + *
  • Invalidated after successful validation; callers must serialize concurrent completion *
* *

Example password reset flow: @@ -69,7 +69,7 @@ public class VigilResetTokenService { * Creates a reset token service with the provided dependencies. * * @param tokenService the token service for JWT operations - * @param blacklistService the blacklist service for single-use enforcement + * @param blacklistService the blacklist service for post-validation invalidation * @param config reset token configuration */ public VigilResetTokenService( @@ -99,7 +99,7 @@ public String generate(String subject) { *

    *
  • Subject (email) *
  • Expiration time - *
  • Unique token ID (jti) for single-use enforcement + *
  • Unique token ID ({@code jti}) for identification *
  • Type claim ("reset") *
* @@ -141,8 +141,9 @@ public String validate(String token) { /** * Validates and consumes a reset token. * - *

After calling this method, the token is blacklisted and cannot be used again. This prevents - * replay attacks. + *

After successful validation, this method blacklists the token against later reuse. + * Validation and blacklisting are separate operations; callers that may process the same token + * concurrently must serialize reset completion or enforce uniqueness in shared storage. * * @param token the reset token * @return the subject (email) from the token diff --git a/src/main/java/io/github/sequelcore/vigil/autoconfigure/VigilProperties.java b/src/main/java/io/github/sequelcore/vigil/autoconfigure/VigilProperties.java index fe74439..daf7eee 100644 --- a/src/main/java/io/github/sequelcore/vigil/autoconfigure/VigilProperties.java +++ b/src/main/java/io/github/sequelcore/vigil/autoconfigure/VigilProperties.java @@ -91,8 +91,8 @@ public record VigilProperties( /** * JWT token configuration. * - * @param secret HMAC signing secret — minimum 32 characters per RFC 8725bis; required when {@code - * algorithm=HS256}, ignored when {@code algorithm=RS256} + * @param secret HMAC signing secret, at least 32 characters; required when {@code + * algorithm=HS256}. Use randomly generated key material with at least 256 bits of entropy. * @param accessTtl access token time-to-live * @param refreshTtl refresh token time-to-live * @param issuer optional token issuer claim ({@code iss}); validated on parse when set @@ -170,7 +170,7 @@ public enum Algorithm { if (algorithm == Algorithm.HS256) { if (secret == null || secret.length() < 32) { throw new IllegalArgumentException( - "vigil.jwt.secret must be at least 32 characters (256 bits) per RFC 8725bis." + "vigil.jwt.secret must be at least 32 characters." + (secret != null ? " Current length: " + secret.length() : " Value is null.")); } } @@ -373,7 +373,8 @@ public record Protection(int maxAttempts, Duration lockDuration, int maxSize) { * Authentication filter configuration. * * @param ignoredPaths paths that bypass ALL processing (no tenant, no auth, no populators) - * @param publicPaths paths that permit anonymous but authenticate if credentials present + * @param publicPaths paths that continue without credentials but authenticate them when present; + * application authorization rules still decide access * @param profilePaths mapping of profile name to path patterns for cookie resolution */ public record Filter( diff --git a/src/main/java/io/github/sequelcore/vigil/filter/VigilAuthenticationFilter.java b/src/main/java/io/github/sequelcore/vigil/filter/VigilAuthenticationFilter.java index 75c2cca..bd80edd 100644 --- a/src/main/java/io/github/sequelcore/vigil/filter/VigilAuthenticationFilter.java +++ b/src/main/java/io/github/sequelcore/vigil/filter/VigilAuthenticationFilter.java @@ -39,8 +39,9 @@ * *

    *
  • Ignored paths: Skip all processing (no tenant, no auth, no populators) - *
  • Public paths: Permit anonymous, but authenticate if credentials present - *
  • Protected paths: Require authentication (Spring Security handles 401) + *
  • Public paths: Continue without credentials, but authenticate them when present; + * Spring Security still decides access + *
  • Other paths: Record missing credentials and let Spring Security decide access *
* *

Flow: @@ -50,8 +51,7 @@ *

  • Extract tenant context from header (if enabled) *
  • Attempt JWT authentication (if credentials present) *
  • Attempt session authentication (if JWT absent/failed and session enabled) - *
  • Authorization decision: proceed based on path type and auth status - *
  • Populate custom contexts via {@link VigilContextPopulator} + *
  • Populate custom contexts according to path type and authentication status * */ public class VigilAuthenticationFilter extends OncePerRequestFilter { @@ -146,7 +146,7 @@ protected void doFilterInternal( // Authenticated - SecurityContext already set by authenticateJwt/authenticateSession populateContexts(request, authenticatedClaims); } else if (isPublicPath(path)) { - // Public path - permit anonymous access + // Public path - continue without credentials; Spring Security still decides access populateContexts(request, null); } else { // Protected path without authentication @@ -354,7 +354,7 @@ protected boolean isIgnoredPath(String path) { } /** - * Checks if the given path permits anonymous access (but authenticates if credentials present). + * Checks if the given path continues without credentials while authenticating them when present. */ protected boolean isPublicPath(String path) { return publicPathMatcher.matches(path);