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

4.9 KiB

18 — Resource server checklist

← Keycloak setup · README

The list for a Spring Security resource server specifically. For the token-minting side, see 10 — Production checklist.

Claims

  • aud is validated. It is not by default. One property, one bean, or one wrapped validator — 12. 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_HOSTNAME17.
  • 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.
  • You know whether your issuer emits typ: at+jwt. The default stack refuses it — 12.
  • setJwtValidator is never called with a bare validator. It replaces the whole stack. Wrap with JwtValidators.createDefaultWithValidators13.
  • The default stack is pinned by a test, so an upgrade that moves it goes red rather than quiet — 13.

Authorities

  • Roles actually arrive. Keycloak's live under realm_access.roles and resource_access.<client>.roles; the default converter reads neither — 14.
  • Hyphenated client ids in SpEL expressions are quoted. ['reports-api'], not [reports-api]. The failure is a silent empty authority list — 14.
  • You have either a JwtAuthenticationConverter bean or the properties, not both. The bean silently disables the properties — 14.
  • Realm and client roles that share a name do not collide into one ROLE_ namespace in a way that grants something — 14.

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.
  • The JWKS cache is not shared with anything else. A refresh calls cache.invalidate(), which clears the whole cache — 15.
  • You know your revocation window. It is the cache TTL, not zero, and not the token lifetime — 15.
  • 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.
  • 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.
  • You have decided about rate limiting. Spring Security disables Nimbus's. Restoring it means building the JWKSource yourself and giving up discovery — 16.
  • You have decided about outage tolerance. It is off. When the cache expires and the issuer is unreachable, every request fails — 15.

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.
  • CSRF is disabled deliberately, because a stateless bearer-token API has no ambient credential to protect — and for no other reason — 04.

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/ is less code and fewer moving parts. See 09 — manual filter vs resource server.

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 · README


If you also own the issuer, the matching list for that side is authorization-server/10.