Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b6XzpJxZ25cDyuw1Uk
2.1 KiB
refresh-tokens
Companion code for JWT Refresh Token Rotation in Spring Security 7 (With Reuse Detection).
Short-lived access JWT, opaque rotating refresh token, token families, reuse detection, logout on one device and everywhere. H2 in memory, no external services.
Run it: mvn test (JDK 25, Spring Boot 4.1.2, Spring Security 7.1.2). Every test writes what it observed to output/, and scripts/run-all.sh regenerates all of it, including a real HTTP walkthrough.
| file | what it shows |
|---|---|
01-rotation.txt |
login, two rotations, one family; the access token carries the family id as sid |
02-storage.txt |
the table holds the SHA-256 of the token, never the token |
03-reuse-detection.txt |
a replayed token revokes the family, including the innocent holder's newest token |
04-families-are-independent.txt |
a replay on the phone does not touch the laptop's login |
05-concurrent-refresh-strict.txt |
8 simultaneous requests with one token: 1 wins, the family is then revoked |
06-concurrent-refresh-grace.txt |
the same stampede with reuse-grace=5s: the family survives, the late replay still kills it |
07-logout.txt |
single-device logout, logout everywhere, and the access token that outlives both |
08-check-sid.txt |
demo.check-sid=true makes the access token die with its family |
09-expiry.txt |
idle timeout versus the absolute family cap |
10-clock-skew.txt |
Spring Security's default validator accepts a token 30 seconds past exp |
11-identical-refusals.txt |
unknown, reused, revoked and expired answer with the same bytes |
12-http-walkthrough.txt |
the same story over real HTTP with curl |
Settings in application.yaml: demo.jwt.access-ttl (5m), demo.refresh.token-ttl (7d), demo.refresh.family-max-age (30d), demo.refresh.reuse-grace (0s), demo.check-sid (false).
Token fingerprints and family ids differ on every run because the tokens are random. Statuses, counts and HTTP codes do not.
The signing secret in application.yaml is a demo value. Read yours from a secret manager.