1
0
asmhatre 4a8dab6739 Spring Security 7.1 JWT authentication on Spring Boot 4.1
Runnable companion for https://ankurm.com/spring-security-7-1-jwt-authentication-guide/

- login -> token issue -> OncePerRequestFilter -> SecurityContext, end to end
- HS256 and RS256 variants (RS256 publishes a real JWKS endpoint)
- the same API secured by the built-in oauth2ResourceServer().jwt(), for comparison
- 11 documentation chapters under docs/, interlinked with the code
- docs/output/ is real captured output, regenerated by scripts/run-all.sh
- 13 passing tests pinning the 401-vs-403 contract and the CSRF failure

Verified against Spring Boot 4.1.1, Spring Security 7.1.1, JDK 25.0.4.1.
2026-08-22 06:34:43 +00:00

jwt-auth-demo

Runnable companion code for Spring Security 7.1 JWT Authentication: The Complete Guide (Spring Boot 4.1) on ankurm.com.

Everything here was compiled and executed. Every file under docs/output/ is real program output, regenerated by scripts/run-all.sh — not transcribed by hand.

JDK Temurin 25.0.4.1+1 (current LTS)
Spring Boot 4.1.1
Spring Framework 7.0.9
Spring Security 7.1.1
Nimbus JOSE+JWT 10.9.1
Tomcat 11.0.24
Jackson 3.1.5 (tools.jackson)

Quickstart

git clone https://ankurm.com/git.app/asmhatre/jwt-auth-demo.git
cd jwt-auth-demo
./scripts/run.sh hs256          # or: mvn spring-boot:run -Dspring-boot.run.profiles=hs256

# in another shell
./scripts/curl-transcript.sh    # the whole flow, end to end

Three demo users:

username password authorities
alice alice-password ROLE_USER, SCOPE_profile:read
root root-password ROLE_USER, ROLE_ADMIN, SCOPE_profile:read, SCOPE_admin:read
locked locked-password locked account — always fails login

Profiles

The same application demonstrates four axes. Combine them freely.

profile what it changes
hs256 (default) Symmetric HMAC signing. One secret signs and verifies.
rs256 RSA signing, plus a real /.well-known/jwks.json endpoint.
(none) Validation by a hand-written OncePerRequestFilter.
resourceserver Validation by Spring Security's built-in oauth2ResourceServer().jwt().
strict Adds the token_type validator to the resource-server chain.
csrfon Turns CSRF on, reproducing the "permitAll() returns 403" failure.
shortlived 2-second access tokens, for observing expiry and clock skew.
trace TRACE logging for org.springframework.security.
./scripts/run.sh rs256
./scripts/run.sh hs256,resourceserver,strict
./scripts/run.sh hs256,csrfon,trace

Endpoints

method path rule why it exists
POST /api/auth/login permitAll() issues an access + refresh token pair
POST /api/auth/refresh permitAll() rotates the refresh token
POST /api/auth/logout authenticated revokes the presented token by jti
GET /api/public/ping permitAll() reachable with no token at all
GET /api/me authenticated 401 without a token
GET /api/admin/stats hasRole('ADMIN') 403 with a valid non-admin token
GET /api/reports @PreAuthorize scope the method-security twin of the above
GET /api/public/filters permitAll() prints the live filter chain
GET /api/async-demo authenticated SecurityContext across a thread boundary
GET /.well-known/jwks.json permitAll() rs256 profile only

Documentation

Start with docs/01-architecture.md and follow the trail.

doc covers
01 — Architecture the whole request path, drawn
02 — Filter chain and ordering where a custom filter goes, and the four ways to place it wrong
03 — 401 vs 403 ExceptionTranslationFilter's actual decision, and RFC 6750 headers
04 — CSRF vs permitAll why permitAll() still returns 403, and when to disable CSRF
05 — HS256 vs RS256 key handling, JWKS, rotation, algorithm confusion
06 — SecurityContext and statelessness explicit save, repositories, thread boundaries
07 — Edge cases 18 things that bite, each with the fix
08 — Testing what to pin, and the Boot 4 test-slice split
09 — Manual filter vs resource server a side-by-side, and which to pick
10 — Production checklist the list to run before you ship
11 — What changed in Spring Security 7 the 7.x-specific surprises this repo hit

Captured output

file what it shows
curl-transcript-hs256.txt 20 steps: login → token → 401 → 403 → tamper → refresh → revoke
rs256-demo.txt JWKS, alg=RS256, signature sizes, tamper rejection
csrf-vs-permitall.txt the 403 on a permitAll() endpoint
csrf-trace.txt the TRACE log proving the chain stops at filter 5 of 12
expiry-and-clock-skew.txt a token still accepted 5s after exp
resource-server-loose.txt a refresh token accepted as an access token
resource-server-strict.txt the same request, refused
test-run.txt 13 passing tests

Regenerate all of it:

./scripts/run-all.sh

Security note

The keys in src/main/resources/ and the HMAC secret in application.yaml are demo values committed on purpose so the repository runs with no setup. They are public. Never point them at anything you care about — see docs/10-production-checklist.md.

License

MIT.

Description
Runnable companion for the ankurm.com guide to Spring Security 7.1 JWT authentication on Spring Boot 4.1 — HS256 + RS256, a hand-written OncePerRequestFilter and the built-in resource server, with real curl transcripts of every failure mode.
Readme MIT 114 KiB
Languages
Java 81.6%
Shell 18.4%