1
0
Files
spring-auth-demo/docs/11-spring-security-7-changes.md
Ankur Mhatre 4dc45d5e00 Add OAuth2 resource server project: JWT validation, JWKS and key rotation
Companion code for the follow-up article. The repository now holds two Maven
projects sharing one docs/ tree:

  jwt-authentication/       the hand-written filter application (unchanged, moved)
  oauth2-resource-server/   a resource server, a Keycloak compose, and a stub
                            issuer whose JWK Set can be mutated on command

The stub exists because Keycloak will not rotate a signing key at a chosen
second, report how many times its JWKS endpoint was fetched, or drop a key from
the published set on request - and the caching and rotation measurements need
all three. The Keycloak run confirms the same code path against a real issuer.

Findings captured under docs/output/, all from real runs:

  * The default validator stack does not check aud. A token minted for another
    service in the same realm is accepted.
  * Spring Security builds its JWKSource with refreshAheadCache(false) and
    rateLimited(false), overriding two of Nimbus's protective defaults, and
    enables Nimbus caching only when NO Spring cache was supplied - so
    supplying one removes the five-minute expiry.
  * A key retired from the JWK Set stops being accepted at t+300s with the
    default cache, and never with a Spring cache that has no TTL.
  * 25 tokens carrying an unknown kid produce 25 JWKS fetches at the issuer,
    through permitAll() endpoints included.
  * A hyphenated client id in an authorities-claim-expression parses as
    subtraction; the SpelEvaluationException is swallowed and logged at TRACE.
  * A clientScopes key in a Keycloak realm import replaces the built-in scopes
    rather than adding to them.

New docs chapters 12-18. README covers both projects. Existing docs and scripts
updated for the new paths; no docs/output/ file from the first article moved, so
links in the published article still resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013f7f2XZXrQ6gW3RtZE187t
2026-08-23 11:00:56 +00:00

5.4 KiB

11 — What changed in Spring Security 7

← production checklist · next: issuer and audience →

Everything below was hit while building this repository against Spring Security 7.1.1 on Spring Boot 4.1.1, JDK 25. Verified by compiling or by reading real responses, not from release notes alone.

FACTOR_BEARER in your authorities

Every bearer-token authentication now carries an extra authority:

"authorities": ["FACTOR_BEARER", "ROLE_USER", "SCOPE_profile:read"]

It backs the new multi-factor authorization support — AuthorizationManagerFactories.multiFactor(), @EnableMultiFactorAuthentication, and in 7.1 the when / withWhen conditions and MultiFactorCondition.WEBAUTHN_REGISTERED. Harmless until a test asserts an exact authority set, or code assumes every authority starts with ROLE_ or SCOPE_.

resource_metadata in every WWW-Authenticate

WWW-Authenticate: Bearer realm="jwt-auth-demo",
    resource_metadata="http://localhost:8080/.well-known/oauth-protected-resource"

Spring Security 7 adds OAuth2ProtectedResourceMetadataFilter to the resource-server chain — RFC 9728, OAuth 2.0 Protected Resource Metadata. Visible in the resource-server chain and in the entry point's output even on the manual profile, because BearerTokenAuthenticationEntryPoint emits it.

7.1 additionally includes charset in WWW-Authenticate (gh-18755).

NimbusJwtEncoder builders (7.0+) and their method names

NimbusJwtEncoder.withSecretKey(secretKey).algorithm(MacAlgorithm.HS256).build();
NimbusJwtEncoder.withKeyPair(rsaPublic, rsaPrivate).algorithm(SignatureAlgorithm.RS256).build();
NimbusJwtEncoder.withKeyPair(ecPublic, ecPrivate).build();

Two traps:

  • the builder method is algorithm(..), not jwsAlgorithm(..) — while the decoder builders use macAlgorithm(..) and signatureAlgorithm(..);
  • there is no keyId(..). Set kid through jwkPostProcessor(jwk -> jwk.keyID(..)).

The pre-7.0 form still compiles:

new NimbusJwtEncoder(new ImmutableSecret<>(secretKey));

setJwkSelector(List::getFirst) (6.5+) resolves the "multiple matching JWKs" exception.

Three sibling classes, three packages

org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint
org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler
org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter

Auto-import will confidently pick the wrong one. This cost a compile cycle here.

Jackson 3

Spring Security 7 moves to Jackson 3 (tools.jackson.*). SecurityJackson2Modules is replaced by SecurityJacksonModules with JsonMapper.Builder. Boot 4.1.1 resolves tools.jackson.core:jackson-databind:3.1.5. If you serialise a SecurityContext — into a session store, a cache, a Redis-backed denylist — that code changes. See the Jackson 2 to 3 migration guide.

Boot 4 test slices moved

@AutoConfigureMockMvc is now org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc, in spring-boot-starter-webmvc-test. Security test support is in spring-boot-starter-security-test. spring-boot-starter-test alone no longer suffices. → doc 08

Boot 4.1: SpEL authority extraction

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          authorities-claim-expressions: "['realm_access']['roles']"
          authority-prefix: "ROLE_"

Mutually exclusive with authorities-claim-name / authorities-claim-delimiter. This is the property-only answer to Keycloak-style nested role claims, which previously needed a custom converter.

csrf.spa() is new in 7.0

.csrf(csrf -> csrf.spa())

One call for CookieCsrfTokenRepository + XorCsrfTokenRequestAttributeHandler + deferred token loading. Checked against the jars: absent from spring-security-config 6.4.7 and 6.5.1, present in 7.0.0. Several guides describe it as a 6.x feature.

Other 7.1 additions worth knowing

  • RestClientOpaqueTokenIntrospector (gh-18745) — the RestClient-based replacement for the RestTemplate introspector, for opaque rather than JWT tokens.
  • ConditionalAuthorizationManager and AllRequiredFactorsAuthorizationManager.anyOf (gh-18960).
  • PreFlightRequestFilter CORS support (gh-18926).
  • InetAddressMatcher (gh-18634).
  • WebAuthn now publishes authentication events (gh-18113).

Migrating from 6.x

Spring Security's own advice: go to 6.5 first, use its opt-in switches to adopt the 7.0 behaviours one at a time, then upgrade. The 6.5 preparation steps exist precisely so that the 7.0 jump is a version bump rather than a rewrite.

ankurm.com has a dedicated Spring Security 5 → 6 → 7 migration guide.