1
0
Files
asmhatre 73ab67b171 Add the filter-chain module
Companion project for "The Spring Security Filter Chain Explained". A real
Spring Boot 4.1.1 servlet application whose scenarios are Spring profiles, plus
a diagnostic controller that prints the live FilterChainProxy, the reflected
FilterOrderRegistration table, and the servlet container's own registrations.

Twelve captured transcripts under docs/output/, nine cross-linked doc chapters,
21 assertions.

Also fixes a broken relative link in method-security/docs/01: the cross-module
reference to context-propagation/README.md needed two levels up, not one.
2026-08-26 02:21:47 +00:00

6.2 KiB

filter-chain — the Spring Security filter chain, printed from a running application

Companion project for The Spring Security Filter Chain Explained on ankurm.com.

Every claim the article makes about filter ordering is produced here by a real Boot application and captured under docs/output/. The chain tables are read back out of the live FilterChainProxy bean; the order numbers are reflected out of spring-security-config's own FilterOrderRegistration. Nothing is transcribed from documentation.

Versions

Version Notes
JDK 25 (Temurin 25.0.4.1+1) current LTS
Spring Boot 4.1.1 inherited as parent, so every version below is Boot-managed
Spring Framework 7.0.9
Spring Security 7.1.1 latest GA; 7.2.0-M1 is a milestone, not a release
Tomcat 11.0.x Boot-managed
JUnit Jupiter 6.x / AssertJ 3.x Boot-managed

Versions were taken from repo1.maven.org/.../maven-metadata.xml, not from release announcements.

Quickstart

./scripts/run.sh baseline        # start with the reference configuration
curl -s localhost:8080/diag/chains
curl -s localhost:8080/diag/order

TRACE=1 ./scripts/run.sh baseline
curl -s -u alice:password localhost:8080/whoami   # then read /tmp/filter-chain-app.log

./scripts/run-all.sh             # every scenario, regenerating docs/output/
mvn test                         # just the 21 assertions
./scripts/stop.sh

Users are alice / password (ROLE_USER) and root / password (ROLE_ADMIN).

Profiles

Each scenario in the article is a profile on this one application.

Profile Configuration Shows
baseline (default) BaselineSecurityConfig The reference 16-filter chain
custom CustomFiltersSecurityConfig Four custom filters at four anchors; the ExceptionTranslationFilter boundary
misordered MisorderedSecurityConfig An authentication filter after AuthorizationFilter — 401 with a valid credential
tie TieSecurityConfig Two filters on one anchor. -DTIE_REVERSED=true flips them
doublereg DoubleRegistrationConfig A filter bean registered twice. Add ,fixed for the cure
multichain MultiChainSecurityConfig Three chains of three different lengths
ignoring IgnoringSecurityConfig WebSecurity.ignoring() — a chain with zero filters

Two JVM flags change behaviour rather than configuration:

Flag Effect
-DTIE_REVERSED=true Swaps the two addFilterBefore calls in the tie profile
-DUNIQUE_ONCE_KEY=true Gives each TenantFilter its own OncePerRequestFilter key, so the second one stops silently skipping itself

Endpoints

Endpoint Purpose
GET /diag/chains Every SecurityFilterChain in FilterChainProxy, with each chain's filters in order
GET /diag/order The whole FilterOrderRegistration table, marking slots whose class is absent
GET /diag/servlet-filters What the container has registered — where double registration shows up
GET /whoami Who the chain decided you are by the time a controller runs
GET /public/hello permitAll
GET /static/asset.txt Under ignoring() in the ignoring profile
GET /api/data Served by the API chain under multichain
POST /hello For provoking a CSRF rejection
GET /tenant/doc, GET /tenant/translated Guarded by TenantFilters either side of ExceptionTranslationFilter
GET /markers Executed order of the tied marker filters

The /diag/* endpoints are the interesting part of this project and also the reason you would never ship it. Delete DiagnosticsController before deploying anything resembling this.

Documentation

Nine chapters under docs/, starting with 01 · The two proxies.

What this module found

Things that are true of Spring Security 7.1.1 and are not in the reference documentation:

  • The default chain is sixteen filters, not the fifteen the docs list — DefaultResourcesFilter (2400) is missing from that sample, and the startup log format has changed too.
  • Orders 300 and 4100 name ChannelProcessingFilter and FilterSecurityInterceptor, both removed in 7.0. The slots were kept so no other number moved.
  • addFilterBefore resolves its anchor against a static table, not against the chain being built — so you can anchor to a filter you have disabled, and it still works.
  • Two filters on the same anchor get the same order number; the tie is broken by List.sort being stable, which is a java.util.List guarantee and not a Spring Security one.
  • Two instances of the same OncePerRequestFilter subclass in one chain share their already-filtered key, and the second one silently never runs.
  • The documented placement for an authorization filter (after AnonymousAuthenticationFilter, 3701) is below ExceptionTranslationFilter (4000), so the AccessDeniedException the documented example throws produces a 500, not a 403.
  • On the /error re-dispatch after a rejection, FilterChainProxy runs the whole chain again, but the six OncePerRequestFilter-based filters skip themselves — so the error page is authorized but not authenticated.
Module Article
context-propagation/ Spring Security Context Propagation
method-security/ Method Security in Spring Security 7

License

MIT — see ../LICENSE.