1
0
Files
spring-security-demo/service-to-service/README.md

104 lines
5.3 KiB
Markdown

# `service-to-service` — token relay, client credentials, exchange and mTLS
Companion project for
[**Securing Spring Boot Microservices: Token Relay, Service-to-Service JWT and mTLS**](https://ankurm.com/spring-boot-microservices-token-relay-mtls/)
on ankurm.com.
Four real processes and a real Spring Authorization Server, so that questions like "whose
identity arrives at the last service?" and "what does that service actually check?" have
transcripts for answers. Everything under [`docs/output/`](docs/output/) was produced by
`scripts/run-all.sh`.
## Versions
| | Version | Notes |
|---|---|---|
| JDK | 25 (Temurin 25.0.4.1+1) | current LTS |
| Spring Boot | 4.1.1 | inherited as parent |
| Spring Framework | 7.0.9 | |
| Spring Security | 7.1.1 | resource server, OAuth2 client, authorization server |
| Spring Cloud | 2025.1.3 (gateway 5.0.3) | **built against Boot 4.0.8** — see [docs/01](docs/01-the-four-processes.md) |
| Tomcat | 11.0.24 | |
Versions were read from `repo1.maven.org/.../maven-metadata.xml`.
## Quickstart
```bash
./scripts/run.sh # all four processes, in order
TOKEN=$(./scripts/user-token.sh) # a real authorization_code + PKCE flow, in curl
./scripts/claims.sh "$TOKEN"
curl -H "Authorization: Bearer $TOKEN" 127.0.0.1:8081/edge/relay
curl -H "Authorization: Bearer $TOKEN" 127.0.0.1:8081/edge/client-credentials
curl -H "Authorization: Bearer $TOKEN" 127.0.0.1:8081/edge/exchange
STRICT=true ./scripts/run.sh # issuer + audience + RFC 9068 validation
./scripts/certs.sh && ./scripts/run-all.sh
mvn test # the 8 validator assertions
./scripts/stop.sh
```
The user is `alice` / `password`.
## Processes
| Process | Port | Main class | Role |
|---|---|---|---|
| authserver | 9000 | [`AuthServerApplication`](src/main/java/com/ankurm/s2s/authserver/AuthServerApplication.java) | Spring Authorization Server. Mints every token used here |
| gateway | 8080 | [`GatewayApplication`](src/main/java/com/ankurm/s2s/gateway/GatewayApplication.java) | Spring Cloud Gateway Server MVC, with and without `TokenRelay` |
| edge | 8081 | [`EdgeApplication`](src/main/java/com/ankurm/s2s/edge/EdgeApplication.java) | Resource server **and** OAuth2 client |
| downstream | 8082 | [`DownstreamApplication`](src/main/java/com/ankurm/s2s/downstream/DownstreamApplication.java) | Resource server. Reports who it thinks is calling |
| mtls | 8443 | [`MtlsApplication`](src/main/java/com/ankurm/s2s/mtls/MtlsApplication.java) | Certificate authentication instead of tokens |
## Endpoints
| Endpoint | Strategy |
|---|---|
| `GET /edge/naive` | No token forwarded — the control |
| `GET /edge/relay` | The incoming bearer token, unchanged |
| `GET /edge/client-credentials` | The edge service's own identity |
| `GET /edge/exchange` | RFC 8693 token exchange |
| `GET /edge/relay-async` | Relay from another thread — the `ThreadLocal` trap |
| `GET /orders` | Downstream. Echoes `sub`, `aud`, `scope`, `client_id`, `cnf` |
| `GET /mtls/whoami` | The verified certificate identity |
| `GET /mtls/trusted-header` | An identity taken from a header, verified by nothing |
## Switches
| Switch | Effect |
|---|---|
| `STRICT=true ./scripts/run.sh` | Authorization server emits RFC 9068 tokens; downstream validates issuer, audience and the required-claim set |
| `RS_LOG_LEVEL` / `CLIENT_LOG_LEVEL` / `AS_LOG_LEVEL` / `GATEWAY_LOG_LEVEL` | `DEBUG` on the corresponding package |
## Documentation
| Chapter | |
|---|---|
| [01](docs/01-the-four-processes.md) | The four processes, a browser flow in curl, and four things that cost time |
| [02](docs/02-three-ways-to-get-a-token.md) | Relay, client credentials, token exchange — and the thread that loses the token |
| [03](docs/03-restclient-interceptors.md) | `OAuth2ClientHttpRequestInterceptor` and which `OAuth2AuthorizedClientManager` |
| [04](docs/04-what-is-not-validated.md) | **What a resource server does not validate by default** |
| [05](docs/05-the-gateway.md) | What `TokenRelay` actually relays |
| [06](docs/06-mtls.md) | Mesh mTLS versus in-application mTLS |
| [07](docs/07-choosing.md) | Choosing, and whether you need any of it |
## Captured output
| File | |
|---|---|
| [01-user-token.txt](docs/output/01-user-token.txt) | A complete authorization_code + PKCE flow, in curl |
| [02-five-strategies.txt](docs/output/02-five-strategies.txt) | Five propagation strategies, one request |
| [03-audience-ignored.txt](docs/output/03-audience-ignored.txt) | A token for another service, accepted with HTTP 200 |
| [04-gateway-token-relay.txt](docs/output/04-gateway-token-relay.txt) | The same route with and without `TokenRelay` |
| [05-strict-validation.txt](docs/output/05-strict-validation.txt) | Strict validation, and what turning it on breaks |
| [06-mtls.txt](docs/output/06-mtls.txt) | Two certificates with the same subject and different issuers |
| [07-tests.txt](docs/output/07-tests.txt) | `mvn test` |
## Related modules
- [`filter-chain/`](../filter-chain/README.md) — where `BearerTokenAuthenticationFilter` sits
- [`context-propagation/`](../context-propagation/README.md) — why `/edge/relay-async` returns 401
- [`cors-csrf/`](../cors-csrf/README.md) — the browser-facing half of the same problem
- [`method-security/`](../method-security/README.md) — turning a scope into an authorization decision