1
0
Files
spring-auth-demo/docs/authorization-server/02-minimum-provider.md
Ankur Mhatre 38c0a5f358 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:12:36 +05:30

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)