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.
102 lines
4.7 KiB
Markdown
102 lines
4.7 KiB
Markdown
[← 01 Versions](01-versions.md) · [index](README.md) · next: [03 — Clients, PKCE and the defaults that moved](03-clients-and-pkce.md)
|
|
|
|
# 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`](../../authorization-server/src-broken/LegacySasConfig.java.txt)
|
|
is that configuration, kept out of the build.
|
|
[`scripts/compile-legacy.sh`](../../authorization-server/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`](../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
|
|
|
|
```java
|
|
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`](../../authorization-server/auth-server/src/main/java/com/ankurm/authserver/config/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`](../../authorization-server/auth-server/src/main/java/com/ankurm/authserver/config/DefaultSecurityConfig.java).
|
|
|
|
**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`](07-diagnostics.md) 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`](../output/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`](../output/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.
|
|
|
|
Next: [03 — Clients, PKCE and the defaults that moved](03-clients-and-pkce.md)
|