1
0

Add virtual-thread, structured-concurrency demos for the Spring Security context propagation guide

Three verified programs (JDK 25, Spring Security 7.1.1, Spring Boot 4.1.1 dependency
versions) backing the ankurm.com post: InheritableThreadLocal across thread models,
@Async on a virtual-thread SimpleAsyncTaskExecutor (DelegatingSecurityContextExecutor
vs ContextPropagatingTaskDecorator), and StructuredTaskScope.fork() propagation.
Captured console output and docs chapters included.
This commit is contained in:
2026-08-24 21:48:47 +05:30
commit b7244ff9a2
13 changed files with 543 additions and 0 deletions

59
README.md Normal file
View File

@@ -0,0 +1,59 @@
# spring-security-demo
Companion repo for [Spring Security Context Propagation - Complete
Guide](https://ankurm.com/spring-security-context-propagation-complete-guide/) on ankurm.com.
Three small, dependency-light programs that answer one question each: does a Spring Security
`SecurityContext` survive a specific thread hand-off? No web server, no HTTP -- every scenario
sets an `Authentication` on the calling thread and checks whether the other side of the hand-off
can see it.
## Verified versions
| Component | Version |
|---|---|
| JDK | 25 (Temurin 25.0.4.1+1), LTS, GA 2025-09-16 |
| Spring Boot (reference target) | 4.1.1 |
| Spring Framework | 7.0.9 |
| Spring Security | 7.1.1 |
| `io.micrometer:context-propagation` | 1.2.1 (as managed by Boot 4.1.1's BOM) |
`StructuredTaskScope` is a **preview API** on JDK 25 (JEP 505, fifth preview) and remains
preview through JDK 26 (JEP 525, sixth preview) -- every build/run command below needs
`--enable-preview`.
## Quickstart
```bash
mvn dependency:build-classpath -Dmdep.outputFile=cp.txt
javac --release 25 --enable-preview -cp "$(cat cp.txt)" -d target/classes $(find src -name '*.java')
java --enable-preview -cp "target/classes:$(cat cp.txt)" com.ankurm.vt.Demo1PlainThreadLocal
```
Or just run everything and regenerate the captured output: `scripts/run-all.sh`.
## What each demo shows
| Demo | Question | Chapter |
|---|---|---|
| `Demo1PlainThreadLocal` | Does `InheritableThreadLocal` behave differently for a pooled platform thread vs. a fresh virtual thread? (No Spring.) | [docs/01](docs/01-inheritable-threadlocal.md) |
| `Demo2AsyncVirtualThreads` | Does the Boot-4.1-style virtual-thread `@Async` executor propagate `SecurityContext`, and what four fixes change? | [docs/02](docs/02-async-virtual-threads.md) |
| `Demo3StructuredConcurrency` | Does a `StructuredTaskScope.fork()` subtask see the parent's `SecurityContext`? | [docs/03](docs/03-structured-concurrency.md) |
## Captured output
Every number and log line in the blog post traces back to one of these, produced by
`scripts/run-all.sh`, not retyped:
- [docs/output/demo1.txt](docs/output/demo1.txt)
- [docs/output/demo2.txt](docs/output/demo2.txt)
- [docs/output/demo3.txt](docs/output/demo3.txt)
## The one-line summary of all three chapters
`SecurityContextHolder` is a `ThreadLocal`. Nothing about virtual threads or structured
concurrency changes that. What changed is that virtual threads are never pooled, so
`MODE_INHERITABLETHREADLOCAL`'s old danger (stale context on a reused pool worker) doesn't
apply to them -- and Spring Security 6.5 gave `SecurityContextHolder` a Micrometer
`ThreadLocalAccessor`, so `ContextPropagatingTaskDecorator` / `ContextSnapshot.wrap(...)` now
propagate it automatically, alongside MDC and tracing context, without a
`DelegatingSecurityContext*` wrapper.