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:
140
README.md
140
README.md
@@ -1,15 +1,21 @@
|
||||
# jwt-auth-demo
|
||||
# spring-auth-demo
|
||||
|
||||
Runnable companion code for two articles on [ankurm.com](https://ankurm.com):
|
||||
Runnable companion code for three articles on [ankurm.com](https://ankurm.com):
|
||||
|
||||
| | article | code |
|
||||
|---|---|---|
|
||||
| 1 | [Spring Security 7.1 JWT Authentication: The Complete Guide (Spring Boot 4.1)](https://ankurm.com/spring-security-7-1-jwt-authentication-guide/) | [`jwt-authentication/`](jwt-authentication) |
|
||||
| 2 | [Spring Security OAuth2 Resource Server: JWT Validation, JWKS and Key Rotation](https://ankurm.com/spring-security-oauth2-resource-server-jwks-key-rotation/) | [`oauth2-resource-server/`](oauth2-resource-server) |
|
||||
| 3 | [Spring Authorization Server: Running Your Own OAuth2 / OIDC Provider](https://ankurm.com/spring-authorization-server-oauth2-oidc-provider/) | [`authorization-server/`](authorization-server) |
|
||||
|
||||
Two Maven projects, one shared [`docs/`](docs) tree. The first mints and verifies its own
|
||||
tokens with a hand-written filter; the second verifies tokens minted by somebody else —
|
||||
a real Keycloak, and a stub issuer whose signing keys can be rotated on command.
|
||||
Three Maven projects, one shared [`docs/`](docs) tree. The first mints and verifies its own
|
||||
tokens with a hand-written filter. The second verifies tokens minted by somebody else —
|
||||
a real Keycloak, and a stub issuer whose signing keys can be rotated on command. The third
|
||||
*is* the somebody else: a real OAuth2 / OIDC provider, with a client and a resource server
|
||||
in front of it.
|
||||
|
||||
> This repository was called `jwt-auth-demo` until the third project landed. Gitea keeps the
|
||||
> old URL redirecting, but please update any bookmarks to `spring-auth-demo`.
|
||||
|
||||
Everything here was compiled and executed. Every file under [`docs/output/`](docs/output) is
|
||||
real program output, regenerated by a script — not transcribed by hand.
|
||||
@@ -23,6 +29,7 @@ real program output, regenerated by a script — not transcribed by hand.
|
||||
| Nimbus JOSE+JWT | **10.9.1** |
|
||||
| Tomcat | **11.0.24** |
|
||||
| Jackson | **3.1.5** (`tools.jackson`) |
|
||||
| Spring Authorization Server | **7.1.1** — the same artifact, now versioned with Spring Security |
|
||||
| Keycloak | **26.7.2** (resource server project only) |
|
||||
| Caffeine | **3.2.4** (resource server project only) |
|
||||
|
||||
@@ -33,8 +40,8 @@ real program output, regenerated by a script — not transcribed by hand.
|
||||
### Project 1 — JWT authentication with a hand-written filter
|
||||
|
||||
```bash
|
||||
git clone https://ankurm.com/git.app/asmhatre/jwt-auth-demo.git
|
||||
cd jwt-auth-demo/jwt-authentication
|
||||
git clone https://ankurm.com/git.app/asmhatre/spring-auth-demo.git
|
||||
cd spring-auth-demo/jwt-authentication
|
||||
./scripts/run.sh hs256 # or: mvn spring-boot:run -Dspring-boot.run.profiles=hs256
|
||||
|
||||
# in another shell
|
||||
@@ -44,7 +51,7 @@ cd jwt-auth-demo/jwt-authentication
|
||||
### Project 2 — OAuth2 resource server, JWKS and rotation
|
||||
|
||||
```bash
|
||||
cd jwt-auth-demo/oauth2-resource-server
|
||||
cd spring-auth-demo/oauth2-resource-server
|
||||
|
||||
# a stub issuer whose JWK Set can be mutated on command
|
||||
./scripts/run-stub-issuer.sh
|
||||
@@ -57,6 +64,20 @@ docker compose -f docker/compose.yaml up -d
|
||||
./scripts/keycloak-demo.sh
|
||||
```
|
||||
|
||||
### Project 3 — your own OAuth2 / OIDC provider
|
||||
|
||||
```bash
|
||||
cd spring-auth-demo/authorization-server
|
||||
|
||||
./scripts/run.sh auth # the provider, :9000
|
||||
./scripts/run.sh rs # an API that trusts it, :8090
|
||||
./scripts/run.sh client # a relying party, :8080
|
||||
|
||||
# then open http://127.0.0.1:8080/orders and log in as alice / password
|
||||
# or drive the whole thing with curl:
|
||||
./scripts/authcode-pkce.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project 1 — `jwt-authentication/`
|
||||
@@ -167,9 +188,70 @@ Regenerate its captured output with `./oauth2-resource-server/scripts/run-all.sh
|
||||
|
||||
---
|
||||
|
||||
## Project 3 — `authorization-server/`
|
||||
|
||||
Three Maven modules, three JVMs, three ports. Nothing about an authorization server is
|
||||
observable without a client to drive the browser redirect and a resource server to accept
|
||||
or reject what comes out.
|
||||
|
||||
| module | port | what it is |
|
||||
|---|---|---|
|
||||
| [`auth-server/`](authorization-server/auth-server) | 9000 | the provider: clients, PKCE, consent, token customisation |
|
||||
| [`resource-server/`](authorization-server/resource-server) | 8090 | an API that trusts its tokens |
|
||||
| [`oidc-client/`](authorization-server/oidc-client) | 8080 | a relying party that logs in and calls the API |
|
||||
|
||||
Two users: `alice` / `password` (`ROLE_USER`, `ROLE_ADMIN`) and `bob` / `password`
|
||||
(`ROLE_USER`).
|
||||
|
||||
Three registered clients:
|
||||
|
||||
| client | secret | authentication | grants |
|
||||
|---|---|---|---|
|
||||
| `demo-web` | `web-secret` | `client_secret_basic` | authorization code + refresh |
|
||||
| `demo-spa` | — | `none` (public) | authorization code + refresh |
|
||||
| `demo-service` | `service-secret` | `client_secret_basic` | client credentials |
|
||||
|
||||
### Profiles
|
||||
|
||||
| module | profile | what it changes |
|
||||
|---|---|---|
|
||||
| auth-server | *(none)* | consent on, PKCE required, custom claims, JWT tokens |
|
||||
| auth-server | `noconsent` | `requireAuthorizationConsent(false)` on every client |
|
||||
| auth-server | `nopkce` | `requireProofKey(false)` on the public client |
|
||||
| auth-server | `noclaims` | the `OAuth2TokenCustomizer` bean is not registered |
|
||||
| auth-server | `opaque` | `demo-service` gets reference tokens instead of JWTs |
|
||||
| auth-server | `acceptall` | the entry-point matcher without `setIgnoredMediaTypes` — see [docs/authorization-server/09](docs/authorization-server/09-entry-point.md) |
|
||||
| auth-server | `trace` | `TRACE` logging for `org.springframework.security` |
|
||||
| resource-server | `noaud` | audience validation off, i.e. the Spring Boot default |
|
||||
| oidc-client | `nopkce` | rebuilds the registration the way Spring Security 6.x would |
|
||||
|
||||
### Endpoints
|
||||
|
||||
| method | path | port | what it is |
|
||||
|---|---|---|---|
|
||||
| `GET` | `/.well-known/openid-configuration` | 9000 | OIDC discovery. Only present because `.oidc(...)` is on |
|
||||
| `GET` | `/.well-known/oauth-authorization-server` | 9000 | the OAuth2 metadata document, always present |
|
||||
| `GET` | `/oauth2/jwks` | 9000 | public keys |
|
||||
| `GET` | `/oauth2/authorize` | 9000 | the authorization endpoint |
|
||||
| `POST` | `/oauth2/token` | 9000 | the token endpoint |
|
||||
| `POST` | `/oauth2/introspect` | 9000 | for opaque tokens |
|
||||
| `GET` | `/oauth2/consent` | 9000 | **our** consent page |
|
||||
| `GET` | `/userinfo` | 9000 | OIDC UserInfo |
|
||||
| `GET` | `/diag/settings`, `/diag/clients`, `/diag/chains` | 9000 | **diagnostics. Delete before shipping** |
|
||||
| `GET` | `/api/orders` | 8090 | needs `SCOPE_orders.read` |
|
||||
| `POST` | `/api/orders` | 8090 | needs `SCOPE_orders.write` |
|
||||
| `GET` | `/api/admin` | 8090 | needs `ROLE_ADMIN`, which only exists via the token customiser |
|
||||
| `GET` | `/whoami` | 8090 | everything the resource server decoded |
|
||||
| `GET` | `/orders` | 8080 | the relying party's page; triggers the whole flow |
|
||||
|
||||
Regenerate its captured output with `./authorization-server/scripts/run-all.sh`
|
||||
(no Docker needed; a few minutes).
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
One numbered trail across both projects. Start at
|
||||
One numbered trail across the first two projects, plus a separate set for the third. Start at
|
||||
[`docs/01-architecture.md`](docs/01-architecture.md).
|
||||
|
||||
| doc | covers |
|
||||
@@ -193,6 +275,24 @@ One numbered trail across both projects. Start at
|
||||
| [17 — Keycloak setup](docs/17-keycloak-setup.md) | compose, realm import, and three ways it bites |
|
||||
| [18 — Resource server checklist](docs/18-resource-server-checklist.md) | the list for the resource-server side |
|
||||
|
||||
### Project 3 — running your own provider
|
||||
|
||||
A separate chapter set, indexed at
|
||||
[`docs/authorization-server/`](docs/authorization-server/README.md).
|
||||
|
||||
| doc | covers |
|
||||
|---|---|
|
||||
| [01 — Versions and the 7.0 move](docs/authorization-server/01-versions.md) | why there is no SAS version to pin, and which starter to use |
|
||||
| [02 — The minimum working provider](docs/authorization-server/02-minimum-provider.md) | two chains, and the API that replaced `applyDefaultSecurity` |
|
||||
| [03 — Clients, PKCE and the defaults that moved](docs/authorization-server/03-clients-and-pkce.md) | `requireProofKey` flipped to `true` on both sides |
|
||||
| [04 — The consent page](docs/authorization-server/04-consent-page.md) | the form contract, and the redirect loop |
|
||||
| [05 — Token customisation](docs/authorization-server/05-token-customisation.md) | the bean the JWT generator looks for, and the one it ignores |
|
||||
| [06 — The resource server side](docs/authorization-server/06-resource-server.md) | what `issuer-uri` does and does not validate |
|
||||
| [07 — Diagnostics](docs/authorization-server/07-diagnostics.md) | reading the effective configuration back out |
|
||||
| [08 — The relying party](docs/authorization-server/08-client.md) | a real browser flow, and the client-side PKCE default |
|
||||
| [09 — Entry point and the Accept header](docs/authorization-server/09-entry-point.md) | why the token endpoint 302s to a login page |
|
||||
| [10 — Should you run one at all](docs/authorization-server/10-should-you.md) | the honest answer |
|
||||
|
||||
---
|
||||
|
||||
## Captured output
|
||||
@@ -229,6 +329,22 @@ One numbered trail across both projects. Start at
|
||||
| [`rs-keycloak-default-converter.txt`](docs/output/rs-keycloak-default-converter.txt) | real Keycloak, roles unmapped |
|
||||
| [`rs-test-run.txt`](docs/output/rs-test-run.txt) | 10 tests pinning the default validator stack |
|
||||
|
||||
### Project 3
|
||||
|
||||
Indexed in full at
|
||||
[`docs/authorization-server/README.md`](docs/authorization-server/README.md). The ones worth
|
||||
opening first:
|
||||
|
||||
| file | what it shows |
|
||||
|---|---|
|
||||
| [`as-settings-defaults.txt`](docs/output/as-settings-defaults.txt) | `requireProofKey` false in SAS 1.5.8 and Spring Security 6.5.1, true in 7.1.1 — both sides |
|
||||
| [`as-legacy-compile-failure.txt`](docs/output/as-legacy-compile-failure.txt) | the pre-7.0 configuration, and the four compiler errors it now produces |
|
||||
| [`as-authcode-pkce.txt`](docs/output/as-authcode-pkce.txt) | the whole authorization-code + PKCE flow, every parameter visible |
|
||||
| [`as-client-flow-nopkce.txt`](docs/output/as-client-flow-nopkce.txt) | a pre-7.0 client against a 7.1 provider, failing on the client's own error page |
|
||||
| [`as-entrypoint-accept.txt`](docs/output/as-entrypoint-accept.txt) | 302 vs 401 from the token endpoint, decided by the `Accept` header |
|
||||
| [`as-client-credentials-opaque.txt`](docs/output/as-client-credentials-opaque.txt) | a reference token, and what introspection returns for it |
|
||||
| [`as-test-run.txt`](docs/output/as-test-run.txt) | 7 contract tests |
|
||||
|
||||
---
|
||||
|
||||
## Security note
|
||||
@@ -244,6 +360,12 @@ them at anything you care about — see
|
||||
`/api/public/decoder` reads private fields by reflection and prints your JWK Set URI and
|
||||
cache timings. It is a diagnostic. Delete it before you ship.
|
||||
|
||||
The authorization server's `/diag/*` endpoints are the same kind of thing: they publish
|
||||
client ids, grant types, scopes and your filter-chain ordering with no authentication. Its
|
||||
signing key is generated fresh on every boot, and its users are hard-coded. Read
|
||||
[docs/authorization-server/10-should-you.md](docs/authorization-server/10-should-you.md)
|
||||
before taking any of it near production.
|
||||
|
||||
## License
|
||||
|
||||
MIT.
|
||||
|
||||
Reference in New Issue
Block a user