spring-security-demo
Companion code for the Spring Security series on ankurm.com. Each
directory is a self-contained Maven project for one article, with its own pom.xml, its own
numbered documentation chapters, and its own captured output under docs/output/ — regenerated
by that module's scripts/run-all.sh, never typed by hand.
| Module | Article | What it demonstrates |
|---|---|---|
context-propagation/ |
Spring Security Context Propagation: The Complete Guide | Whether a SecurityContext survives @Async, executors, virtual threads, StructuredTaskScope, Reactor, schedulers and the servlet filter chain |
method-security/ |
Method Security in Spring Security 7: @PreAuthorize, @PostAuthorize and the Proxy Traps |
What the method-security annotations do, the full SpEL surface, and the cases where the check silently does not run |
filter-chain/ |
The Spring Security Filter Chain Explained | Every filter in the default chain and its order number, where a custom filter actually lands, and how to read the TRACE log |
cors-csrf/ |
CORS, CSRF and SameSite in Spring Boot 4 | Why MVC-layer CORS does not fix a security-layer preflight rejection, what csrf.spa() assigns, and the cookie a browser silently refuses to store |
service-to-service/ |
Securing Spring Boot Microservices: Token Relay, Service-to-Service JWT and mTLS | Whose identity arrives at the last service under relay, client credentials and token exchange — and what a resource server does not check by default |
ssrf/ |
HTTP Client SSRF Mitigation in Spring Boot 4.1 | A working SSRF exploit against a link-preview endpoint, and the InetAddressFilter that stops it — including the two ways of configuring it that silently do the opposite |
They are related more closely than they look. filter-chain is about how an Authentication
gets into SecurityContextHolder in the first place and in what order; context-propagation is
about whether it survives leaving the request thread; method-security reads it back on whatever
thread it ends up on. An @Async method carrying @PreAuthorize fails with
AuthenticationCredentialsNotFoundException for reasons that belong to the second module, not
the third — and a custom authentication filter that never populated the context in the first
place fails the same way, for reasons that belong to the first.
ssrf is the outbound counterpart to all of them. Every other module asks what a request
arriving at this application is allowed to do; this one asks where this application is allowed
to send a request, which turns out to be the question an attacker cares about once they have
found an endpoint that fetches a URL. Its filter is not part of Spring Security at all — it
is a Boot 4.1 HTTP-client control — and that is worth noticing, because a SecurityFilterChain
has nothing to say about it.
service-to-service is the same question one process further out: cors-csrf and
context-propagation ask whether an identity survives a thread or a browser boundary, and this
one asks whether it survives an HTTP boundary — and what the service on the far side bothers to
verify about it. Its /edge/relay-async endpoint fails for exactly the reason
context-propagation documents.
cors-csrf is where those order numbers stop being trivia. CorsFilter at 1000 and CsrfFilter
at 1100 both sit far above AuthorizationFilter at 4200, and almost every confusing symptom in
that module is a consequence of one of those three positions — including a 403 that arrives as a
401 because the container re-dispatched the request to /error and the chain ran a second time.
Common ground
All modules target the same verified stack: JDK 25 (Temurin 25.0.4.1+1),
Spring Framework 7.0.9, Spring Security 7.1.1 — the versions Spring Boot 4.1.1
manages. Versions were taken from maven-metadata.xml on Maven Central rather than from
release announcements.
context-propagation additionally needs --enable-preview, because StructuredTaskScope is
still a preview API on JDK 25. method-security does not. filter-chain, cors-csrf and
service-to-service are real servlet applications: they inherit spring-boot-starter-parent
and run on Tomcat, because the things they demonstrate only exist inside a servlet container.
service-to-service runs five of them at once, and is the only module that also pulls in
Spring Cloud — a separate release train, built against Boot 4.0.8 rather than 4.1.1.
Running a module
cd cors-csrf # or context-propagation, method-security, filter-chain, service-to-service
./scripts/run-all.sh # every demo plus the test suite, regenerating docs/output/
mvn test # just the assertions
filter-chain and cors-csrf also have ./scripts/run.sh <profile> and ./scripts/stop.sh,
because their scenarios are a running web application rather than a main() method.
cors-csrf adds ./scripts/preflight.sh, which sends one CORS preflight and prints the headers
that decide the outcome.
License
MIT — see LICENSE.