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.
This commit is contained in:
101
docs/authorization-server/02-minimum-provider.md
Normal file
101
docs/authorization-server/02-minimum-provider.md
Normal file
@@ -0,0 +1,101 @@
|
||||
[← 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)
|
||||
Reference in New Issue
Block a user