|
| 1 | +# Async and streaming security |
| 2 | + |
| 3 | +Vigil preserves an authenticated Spring Security context for the lifetime of one servlet request, |
| 4 | +including its `ASYNC` and `ERROR` redispatches. It does not create an `HttpSession`, revalidate a |
| 5 | +JWT during redispatch, or weaken the application's authorization rules. |
| 6 | + |
| 7 | +## Secure stateless configuration |
| 8 | + |
| 9 | +Use Vigil's request-scoped repository in the application's filter chain. Keep authorization rules |
| 10 | +application-owned and continue authorizing every dispatcher type. |
| 11 | + |
| 12 | +```java |
| 13 | +@Bean |
| 14 | +SecurityFilterChain securityFilterChain( |
| 15 | + HttpSecurity http, |
| 16 | + VigilAuthenticationFilter vigilAuthenticationFilter) throws Exception { |
| 17 | + var requestSecurityContextRepository = new RequestAttributeSecurityContextRepository(); |
| 18 | + vigilAuthenticationFilter.setSecurityContextRepository(requestSecurityContextRepository); |
| 19 | + return http |
| 20 | + .sessionManagement(session -> |
| 21 | + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) |
| 22 | + .securityContext(context -> |
| 23 | + context.securityContextRepository(requestSecurityContextRepository)) |
| 24 | + .authorizeHttpRequests(authorize -> authorize |
| 25 | + .requestMatchers("/auth/**").permitAll() |
| 26 | + .anyRequest().authenticated()) |
| 27 | + .addFilterBefore(vigilAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) |
| 28 | + .build(); |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +Vigil validates credentials and runs authentication hooks and context populators only on the |
| 33 | +initial `REQUEST`. After successful authentication it saves the `SecurityContext` in a |
| 34 | +`RequestAttributeSecurityContextRepository`. Spring Security's `SecurityContextHolderFilter` |
| 35 | +loads that context for a legitimate redispatch and clears its thread-local holder afterward. A |
| 36 | +new request, or a fabricated `ASYNC`/`ERROR` dispatch without the request attribute, has no saved |
| 37 | +identity and remains subject to normal authorization. |
| 38 | + |
| 39 | +Do not globally `permitAll` `ASYNC` or `ERROR` merely to avoid a secondary authorization failure. |
| 40 | +That can bypass application policy. Any deliberate narrow exception remains application-owned. |
| 41 | + |
| 42 | +## MVC lifecycle |
| 43 | + |
| 44 | +`DeferredResult`, `ResponseBodyEmitter`, `SseEmitter`, and `StreamingResponseBody` use Servlet |
| 45 | +async processing. MVC leaves the response open after the initial dispatch and later performs an |
| 46 | +`ASYNC` dispatch to finish processing. When an emitter write fails because the client disconnected, |
| 47 | +the application must not call `complete` or `completeWithError`; the container notifies Spring MVC, |
| 48 | +which performs the final error dispatch and cleanup. |
| 49 | + |
| 50 | +A Broken pipe is a normal network event and cannot be prevented. Record expected disconnects |
| 51 | +separately from integrity failures. Monitor emitter completion, timeout, active connections, and |
| 52 | +unexpected exception-resolver failures. The application owns MVC executors, timeouts, heartbeats, |
| 53 | +resource cleanup, and propagation of domain context. Vigil preserves the Spring Security principal, |
| 54 | +not arbitrary application `ThreadLocal` values. |
| 55 | + |
| 56 | +## Responsibility matrix |
| 57 | + |
| 58 | +| Vigil | Consuming application | |
| 59 | +| --- | --- | |
| 60 | +| Validate the initial credential and save its authenticated context on the same request | Define HTTP and business authorization rules | |
| 61 | +| Avoid reauthentication and authentication side effects on redispatch | Configure MVC async lifecycle and resource cleanup | |
| 62 | +| Save into Spring Security's request-attribute repository contract | Install a `RequestAttributeSecurityContextRepository` in `HttpSecurity` | |
| 63 | +| Fail closed without evidence of an authenticated initial request | Decide and test any narrow dispatcher-type exceptions | |
| 64 | +| Preserve the Spring Security principal across dispatch threads | Propagate additional tenant/domain context when required | |
| 65 | + |
| 66 | +## Source-backed decisions |
| 67 | + |
| 68 | +The complete auditable research record, including upstream source/tests, issue evidence, and the |
| 69 | +alternatives matrix, is in [async and streaming security research](../research/async-streaming-security-sources.md). |
| 70 | + |
| 71 | +- [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. |
| 72 | +- [Spring Framework async MVC](https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-ann-async.html): documents final dispatch and emitter `IOException` handling. |
| 73 | +- [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. |
| 74 | +- [Spring Security authorization](https://docs.spring.io/spring-security/reference/7.0/servlet/authorization/authorize-http-requests.html): dispatcher authorization remains application policy. |
| 75 | +- [Spring Security issue 12758](https://github.com/spring-projects/spring-security/issues/12758): maintainers prescribe this repository for the equivalent JWT and `StreamingResponseBody` failure. |
| 76 | +- [Spring Framework issue 33439](https://github.com/spring-projects/spring-framework/issues/33439): disconnect timing is network/container dependent. |
| 77 | + |
| 78 | +## Migration |
| 79 | + |
| 80 | +Synchronous integrations keep their behavior. Async applications must install a |
| 81 | +`RequestAttributeSecurityContextRepository` in `HttpSecurity` as shown above. Remove broad |
| 82 | +`dispatcherTypeMatchers(ASYNC, ERROR).permitAll()` workarounds after verifying application error |
| 83 | +routes. No token, cookie, route, or authorization contract changes are required. |
0 commit comments