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.
4.7 KiB
← 01 Versions · index · next: 03 — Clients, PKCE and the defaults that moved
The minimum working provider
The two imports that break every tutorial
Two classes moved out of the Spring Authorization Server jar and into
spring-security-config:
| 1.5.8 | 7.1.1 | |
|---|---|---|
OAuth2AuthorizationServerConfiguration |
o.s.s.oauth2.server.authorization.config.annotation.web.configuration |
o.s.s.config.annotation.web.configuration |
OAuth2AuthorizationServerConfigurer |
o.s.s.oauth2.server.authorization.config.annotation.web.configurers |
o.s.s.config.annotation.web.configurers.oauth2.server.authorization |
And one method was deleted. javap on both jars:
# 1.5.8
public static void applyDefaultSecurity(HttpSecurity) throws Exception;
# 7.1.1
(absent)
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http) is the one-liner in
essentially every article and sample written before September 2025. It is gone.
src-broken/LegacySasConfig.java.txt
is that configuration, kept out of the build.
scripts/compile-legacy.sh compiles
it against the real 7.1.1 classpath and commits the compiler's own words to
docs/output/as-legacy-compile-failure.txt:
error: package org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration does not exist
error: package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers does not exist
error: cannot find symbol
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
symbol: variable OAuth2AuthorizationServerConfiguration
4 errors
Four errors from nine lines of copied configuration.
What replaces it
OAuth2AuthorizationServerConfigurer authorizationServer =
new OAuth2AuthorizationServerConfigurer();
http
.securityMatcher(authorizationServer.getEndpointsMatcher())
.with(authorizationServer, server -> server
.oidc(Customizer.withDefaults())
.authorizationEndpoint(endpoint -> endpoint.consentPage("/oauth2/consent")))
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.exceptionHandling(...)
.oauth2ResourceServer(rs -> rs.jwt(Customizer.withDefaults()));
Source:
AuthorizationServerConfig.java.
Why two filter chains
The protocol chain carries securityMatcher(getEndpointsMatcher()), so it declines every
request that is not an OAuth2 or OIDC endpoint. Something has to serve the login form and
the consent page, and it needs a completely different authentication mechanism — a
browser session rather than a bearer token. That is
DefaultSecurityConfig.
Order is load-bearing. The protocol chain must be @Order(HIGHEST_PRECEDENCE). Swap
them and the catch-all form-login chain matches /oauth2/token first: a token request 302s
to /login and the token endpoint is never reached. That redirect is the fingerprint.
/diag/chains prints the live ordering.
OIDC is not on by default
.oidc(Customizer.withDefaults()) is one line and omitting it costs you /userinfo, the
id_token, and /.well-known/openid-configuration. You still get the OAuth2 metadata
document at /.well-known/oauth-authorization-server — the two are different
documents, and as-discovery.txt prints both.
The bean that is not a bean
A custom consent page needs to read OAuth2AuthorizationConsentService. It is not exposed
as an injectable bean. The configurer creates one for its own use; a controller that
constructor-injects it fails the context at startup, and the real message is kept in
as-missing-consent-service.txt:
No qualifying bean of type 'org.springframework.security.oauth2.server.authorization
.OAuth2AuthorizationConsentService' available: expected at least 1 bean which qualifies
as autowire candidate.
Declare OAuth2AuthorizationService and OAuth2AuthorizationConsentService yourself. That
also forces the storage decision into the open: the in-memory implementations mean a second
replica of the authorization server cannot complete a code exchange started on the first.