1
0
Files
spring-auth-demo/docs/18-resource-server-checklist.md
Ankur Mhatre e9381dc5be Add Spring Authorization Server project: OAuth2/OIDC provider, client and resource server
Three modules on Spring Boot 4.1.1 with Spring Authorization Server 7.1.1: the provider
itself, a relying party, and an API that trusts its tokens. Client registration, PKCE,
a custom consent page and token customisation, with profiles that make each failure
reproducible.

Every claim is backed by captured output in docs/output/as-*.txt, regenerated by
authorization-server/scripts/run-all.sh. Notable findings, verified against the jars:

  - OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(HttpSecurity) was deleted
    in 7.0, and both configuration classes moved into spring-security-config
  - ClientSettings.requireProofKey flipped from false to true, on the authorization server
    (1.5.8 -> 7.1.1) and on the OAuth2 client (6.5.1 -> 7.1.1)
  - requireProofKey(false) does not make PKCE optional for a public client; the code
    verifier is that client's only authentication at the token endpoint
  - MediaTypeRequestMatcher(TEXT_HTML) matches Accept: */*, so the token endpoint answers
    API callers with 302 -> /login unless setIgnoredMediaTypes(ALL) is called

Also renames the repository to spring-auth-demo and cross-links the new chapter set from
the existing documentation.
2026-08-24 08:20:38 +05:30

88 lines
4.9 KiB
Markdown

# 18 — Resource server checklist
[← Keycloak setup](17-keycloak-setup.md) · [README](../README.md)
The list for a Spring Security resource server specifically. For the token-minting side, see
[10 — Production checklist](10-production-checklist.md).
## Claims
- [ ] **`aud` is validated.** It is not by default. One property, one bean, or one wrapped
validator — [12](12-issuer-and-audience.md). Without it, any token from your realm
works against any of your services.
- [ ] **`issuer-uri` matches the issuer string byte for byte**, from wherever the resource
server runs. Trailing slashes count. Pin `KC_HOSTNAME` — [17](17-keycloak-setup.md).
- [ ] **The clock skew is a decision.** 60 seconds by default, in both directions. If your
revocation story is short-lived tokens, the real worst case is the lifetime plus a
minute — [12](12-issuer-and-audience.md).
- [ ] **You know whether your issuer emits `typ: at+jwt`.** The default stack refuses it —
[12](12-issuer-and-audience.md).
- [ ] **`setJwtValidator` is never called with a bare validator.** It replaces the whole
stack. Wrap with `JwtValidators.createDefaultWithValidators` — [13](13-validator-stack.md).
- [ ] **The default stack is pinned by a test**, so an upgrade that moves it goes red rather
than quiet — [13](13-validator-stack.md).
## Authorities
- [ ] **Roles actually arrive.** Keycloak's live under `realm_access.roles` and
`resource_access.<client>.roles`; the default converter reads neither —
[14](14-authentication-converter.md).
- [ ] **Hyphenated client ids in SpEL expressions are quoted.** `['reports-api']`, not
`[reports-api]`. The failure is a silent empty authority list — [14](14-authentication-converter.md).
- [ ] **You have either a `JwtAuthenticationConverter` bean or the properties, not both.**
The bean silently disables the properties — [14](14-authentication-converter.md).
- [ ] **Realm and client roles that share a name do not collide** into one `ROLE_`
namespace in a way that grants something — [14](14-authentication-converter.md).
## JWKS and rotation
- [ ] **If you supplied a Spring `Cache`, it has a TTL.** Supplying one disables Nimbus's
five-minute cache; a `ConcurrentMapCache` never expires and a retired key stays
trusted — [15](15-jwks-caching-and-rotation.md).
- [ ] **The JWKS cache is not shared with anything else.** A refresh calls
`cache.invalidate()`, which clears the whole cache — [15](15-jwks-caching-and-rotation.md).
- [ ] **You know your revocation window.** It is the cache TTL, not zero, and not the token
lifetime — [15](15-jwks-caching-and-rotation.md).
- [ ] **Your issuer's rotation leaves a publish window** long enough for every resource
server to see the new key before it starts signing with it —
[15](15-jwks-caching-and-rotation.md).
- [ ] **Someone is watching the JWKS endpoint's request rate.** A rate that tracks request
volume rather than instance count means unknown-`kid` traffic is amplifying through
you — [16](16-jwks-amplification.md).
- [ ] **You have decided about rate limiting.** Spring Security disables Nimbus's. Restoring
it means building the `JWKSource` yourself and giving up discovery —
[16](16-jwks-amplification.md).
- [ ] **You have decided about outage tolerance.** It is off. When the cache expires and the
issuer is unreachable, every request fails — [15](15-jwks-caching-and-rotation.md).
## Endpoints
- [ ] **`/api/public/decoder` is deleted.** The diagnostic in this repository reveals your
JWK Set URI and cache timings to anyone who can reach it.
- [ ] **You know `/.well-known/oauth-protected-resource` exists.** Spring Security 7
publishes it, unauthenticated, without being asked — [12](12-issuer-and-audience.md).
- [ ] **CSRF is disabled deliberately**, because a stateless bearer-token API has no
ambient credential to protect — and for no other reason
— [04](04-csrf-permitall-403.md).
## Should you build this at all
If your services are minting their own tokens for their own users, you do not need a
resource server and you do not need an identity provider; the hand-written filter in
[`jwt-authentication/`](../jwt-authentication) is less code and fewer moving parts. See
[09 — manual filter vs resource server](09-manual-filter-vs-resource-server.md).
The resource server earns its complexity when tokens come from somewhere you do not control:
several services trusting one issuer, an identity provider you did not write, key rotation
that has to happen without redeploying anything. If that is not your situation, most of this
document is a list of ways to get something wrong that you could simply not have.
---
[← Keycloak setup](17-keycloak-setup.md) · [README](../README.md)
---
If you also own the issuer, the matching list for that side is
[`authorization-server/10`](authorization-server/10-should-you.md).