75 lines
3.2 KiB
Markdown
75 lines
3.2 KiB
Markdown
# 5. The gateway, and what `TokenRelay` actually relays
|
|
|
|
*Prev: [4. What is not validated](04-what-is-not-validated.md) · Next: [6. mTLS](06-mtls.md)*
|
|
|
|
Spring Cloud Gateway Server MVC ships a filter that sounds like it solves the whole problem:
|
|
|
|
```yaml
|
|
filters:
|
|
- TokenRelay=
|
|
```
|
|
|
|
It is `TokenRelayFilterFunctions`, with two forms:
|
|
|
|
```java
|
|
public static HandlerFilterFunction<ServerResponse, ServerResponse> tokenRelay();
|
|
public static HandlerFilterFunction<ServerResponse, ServerResponse> tokenRelay(String clientRegistrationId);
|
|
```
|
|
|
|
The documentation is precise about what it does, and it is not what the name suggests to most
|
|
readers: it takes the access token **of the currently authenticated user** — the one obtained
|
|
by `oauth2Login()` — or of the named client registration, and puts it in the `Authorization`
|
|
header of the proxied request.
|
|
|
|
It does not mean "forward the incoming bearer token". There is nothing to forward from if the
|
|
gateway never performed a login.
|
|
|
|
## The A/B
|
|
|
|
`gateway.yml` defines two routes to the same destination, differing only in the filter.
|
|
[`docs/output/04-gateway-token-relay.txt`](output/04-gateway-token-relay.txt):
|
|
|
|
```
|
|
/edge/relay (TokenRelay=) → 200, sub: alice
|
|
/norelay/x (no TokenRelay) → 200, sub: alice
|
|
```
|
|
|
|
Identical. The token arrived because the gateway proxied the `Authorization` header the caller
|
|
sent, which it would have done anyway. In this configuration `TokenRelay` contributes nothing.
|
|
|
|
Where it earns its place is the other shape: a gateway that terminates a **browser session**
|
|
with `oauth2Login()`, keeps the tokens server-side, hands the browser nothing but a session
|
|
cookie, and attaches a token on the way through. That is the backend-for-frontend pattern, and
|
|
it is a genuinely good answer to "where do I keep the token in a SPA" — because the answer is
|
|
"not in the SPA".
|
|
|
|
## The 401 that has nothing to do with OAuth
|
|
|
|
Before `GatewaySecurityConfig` existed, every call through the gateway came back:
|
|
|
|
```
|
|
HTTP/1.1 401
|
|
WWW-Authenticate: Basic realm="Realm", charset="UTF-8"
|
|
```
|
|
|
|
A perfectly valid bearer token, rejected by a gateway that had never been told to expect one.
|
|
Spring Boot applies a default filter chain — HTTP Basic and form login over every path — to any
|
|
application on the classpath with Spring Security and no `SecurityFilterChain` bean. A gateway
|
|
is an application. `WWW-Authenticate: Basic` in front of a token-based estate always means this.
|
|
|
|
## Choosing where the gateway sits
|
|
|
|
| Shape | Gateway does | Downstream sees |
|
|
|---|---|---|
|
|
| Pass-through | Routes; validates nothing | The caller's token; each service validates it |
|
|
| Edge validation | Validates the token, strips it, adds its own identity | The gateway's identity |
|
|
| BFF | `oauth2Login()`, holds tokens, `TokenRelay` | The user's token, obtained by the gateway |
|
|
|
|
Pass-through is the default and is fine while every service validates properly — chapter 4 is
|
|
about how often that assumption is wrong. Edge validation is the one that tempts teams into
|
|
"the gateway checked it, so we can trust the header", which is chapter 6's failure mode wearing
|
|
a different hat.
|
|
|
|
---
|
|
*Prev: [4. What is not validated](04-what-is-not-validated.md) · Next: [6. mTLS](06-mtls.md)*
|