Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ include migration notes.

## Unreleased

## 7.2.0 - 2026-07-12

- Added stateless Spring MVC async and streaming support based on a request-attribute
`SecurityContextRepository`, preserving identity across legitimate redispatches without
revalidating JWTs or creating an `HttpSession`.
- Added integration coverage and migration guidance for Spring MVC async return types and
dispatcher authorization.

## 7.1.1 - 2026-07-10

- Updated the Gradle wrapper to 9.6.1, Spotless to 8.8.0, Caffeine to 3.2.4,
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ Vigil provides JWT lifecycle, request authentication, cookie helpers, tenant con

## Compatibility

Vigil `7.1.x` supports Java 25, Spring Boot 4.1.x, Spring Framework 7.x, Spring Security 7.1.x, Gradle 9.6.x, and Jackson 3. Vigil `6.0.x` was the final Java 21 / Spring Boot 3.5 line.
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.

`7.1.1` is the current release line. Public consumers should pin an exact version and review the release notes before upgrading.
`7.2.0` is the current release line. Public consumers should pin an exact version and review the release notes before upgrading.

## Install

```kotlin
dependencies {
implementation("io.github.sequelcore:vigil-spring-boot-starter:7.0.0")
implementation("io.github.sequelcore:vigil-spring-boot-starter:7.2.0")
}
```

Expand All @@ -43,6 +46,7 @@ The application must still configure route authorization. `ignored-paths` skips
Start at the [documentation index](docs/README.md).

- [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)
Expand Down
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = "io.github.sequelcore"
version = "7.1.1"
version = "7.2.0"

val hasSigningConfiguration = providers.gradleProperty("signingInMemoryKey").isPresent
|| providers.gradleProperty("signing.secretKeyRingFile").isPresent
Expand Down Expand Up @@ -58,6 +58,7 @@ dependencies {

// Testing
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test")
testImplementation("org.springframework.boot:spring-boot-starter-security")
testImplementation("org.springframework.boot:spring-boot-starter-validation")
testImplementation("org.springframework.security:spring-security-test")
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Vigil is a Spring Boot starter for application-owned JWT authentication. It prov

## 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.
- [Configuration reference](reference/configuration.md) — every `vigil.*` property and its defaults.
- [System boundaries](architecture/system-boundaries.md) — understand ownership and extension points before integrating.
Expand Down
10 changes: 10 additions & 0 deletions docs/development/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,14 @@ Changes must prove the behavior at the owning boundary:
- step-up: actor separation, credential failure/lockout, tenant/audience/purpose binding, expiry, and one-time consumption;
- configuration: invalid security settings fail fast at startup.

Async security changes additionally require the real filter chain tests in
`VigilAsyncSecurityIntegrationTest` and the embedded-Tomcat socket tests in
`VigilSseDisconnectTomcatIntegrationTest`. The latter verifies a committed SSE response, a client
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.

Use application integration tests for application-owned routes and user persistence. Vigil tests do not replace product authorization or user-lifecycle tests.
83 changes: 83 additions & 0 deletions docs/guides/async-streaming-security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Async and streaming security

Vigil preserves an authenticated Spring Security context for the lifetime of one servlet request,
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

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();
}
```

Vigil validates credentials and runs authentication hooks and context populators only on the
initial `REQUEST`. After successful authentication it saves the `SecurityContext` in a
`RequestAttributeSecurityContextRepository`. Spring Security's `SecurityContextHolderFilter`
loads that context for a legitimate redispatch and clears its thread-local holder afterward. A
new request, or a fabricated `ASYNC`/`ERROR` dispatch without the request attribute, has no saved
identity and remains subject to normal authorization.

Do not globally `permitAll` `ASYNC` or `ERROR` merely to avoid a secondary authorization failure.
That can bypass application policy. Any deliberate narrow exception remains application-owned.

## MVC lifecycle

`DeferredResult`, `ResponseBodyEmitter`, `SseEmitter`, and `StreamingResponseBody` use Servlet
async processing. MVC leaves the response open after the initial dispatch and later performs an
`ASYNC` dispatch to finish processing. When an emitter write fails because the client disconnected,
the application must not call `complete` or `completeWithError`; the container notifies Spring MVC,
which performs the final error dispatch and cleanup.

A Broken pipe is a normal network event and cannot be prevented. Record expected disconnects
separately from integrity failures. Monitor emitter completion, timeout, active connections, and
unexpected exception-resolver failures. The application owns MVC executors, timeouts, heartbeats,
resource cleanup, and propagation of domain context. Vigil preserves the Spring Security principal,
not arbitrary application `ThreadLocal` values.

## Responsibility matrix

| Vigil | Consuming application |
| --- | --- |
| Validate the initial credential and save its authenticated context on the same request | Define HTTP and business authorization rules |
| Avoid reauthentication and authentication side effects on redispatch | Configure MVC async lifecycle and resource cleanup |
| Save into Spring Security's request-attribute repository contract | Install a `RequestAttributeSecurityContextRepository` in `HttpSecurity` |
| Fail closed without evidence of an authenticated initial request | Decide and test any narrow dispatcher-type exceptions |
| Preserve the Spring Security principal across dispatch threads | Propagate additional tenant/domain context when required |

## 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.

## Migration

Synchronous integrations keep their behavior. Async applications must install a
`RequestAttributeSecurityContextRepository` in `HttpSecurity` as shown above. Remove broad
`dispatcherTypeMatchers(ASYNC, ERROR).permitAll()` workarounds after verifying application error
routes. No token, cookie, route, or authorization contract changes are required.
11 changes: 10 additions & 1 deletion docs/guides/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ Vigil auto-configures `VigilAuthenticationFilter`. Add it inside the application
```java
@Bean
SecurityFilterChain securityFilterChain(
HttpSecurity http, VigilAuthenticationFilter vigilAuthenticationFilter) throws Exception {
HttpSecurity http,
VigilAuthenticationFilter vigilAuthenticationFilter) throws Exception {
var requestSecurityContextRepository = new RequestAttributeSecurityContextRepository();
vigilAuthenticationFilter.setSecurityContextRepository(requestSecurityContextRepository);
return http
.securityContext(context ->
context.securityContextRepository(requestSecurityContextRepository))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated())
Expand All @@ -33,6 +38,10 @@ SecurityFilterChain securityFilterChain(
}
```

The request-scoped repository 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`.

## 3. Issue tokens after application credential validation
Expand Down
10 changes: 5 additions & 5 deletions docs/releases/release-policy.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Release Policy

Vigil is published as a Spring Boot starter for JWT authentication. Version
`7.1.1` is the current release line and Spring Boot 4.1 certification baseline.
`7.2.0` is the current release line and Spring Boot 4.1 certification baseline.

Vigil is used by Sequel applications, but public consumers should pin exact
versions, read release notes, and review migration notes before upgrades.
Expand Down Expand Up @@ -35,15 +35,15 @@ between releases when tests and public behavior remain stable.
Current tested compatibility envelope:

- Java 25;
- Spring Boot 4.1.x;
- Spring Framework 7.x through Spring Boot 4.1.x;
- Spring Security 7.1.x through Spring Boot 4.1.x;
- 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.0.x` is the active supported platform line. Vigil `6.0.x` was the
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
Expand Down
Loading
Loading