Add the service-to-service module
This commit is contained in:
77
service-to-service/docs/03-restclient-interceptors.md
Normal file
77
service-to-service/docs/03-restclient-interceptors.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 3. `RestClient` interceptors
|
||||
|
||||
*Prev: [2. Three ways to get a token](02-three-ways-to-get-a-token.md) · Next: [4. What a resource server does not validate](04-what-is-not-validated.md)*
|
||||
|
||||
`OAuth2ClientHttpRequestInterceptor`, in
|
||||
`org.springframework.security.oauth2.client.web.client`, is the framework's answer for a
|
||||
`RestClient` that needs a token. Its whole public surface:
|
||||
|
||||
```java
|
||||
public OAuth2ClientHttpRequestInterceptor(OAuth2AuthorizedClientManager manager);
|
||||
public void setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler handler);
|
||||
public void setClientRegistrationIdResolver(ClientRegistrationIdResolver resolver);
|
||||
public void setPrincipalResolver(PrincipalResolver resolver);
|
||||
```
|
||||
|
||||
Wiring it takes one line, and choosing the registration per call takes one more:
|
||||
|
||||
```java
|
||||
RestClient client = builder
|
||||
.baseUrl("http://127.0.0.1:8082")
|
||||
.requestInterceptor(new OAuth2ClientHttpRequestInterceptor(authorizedClientManager))
|
||||
.build();
|
||||
|
||||
client.get().uri("/orders")
|
||||
.attributes(clientRegistrationId("edge-service")) // static import
|
||||
.retrieve().body(Map.class);
|
||||
```
|
||||
|
||||
`clientRegistrationId(..)` is a static method on `RequestAttributeClientRegistrationIdResolver`.
|
||||
Without it, the default resolver finds nothing and the request goes out unauthenticated.
|
||||
|
||||
## Which `OAuth2AuthorizedClientManager`
|
||||
|
||||
This is the choice that decides whether the thing works off a request thread.
|
||||
|
||||
| Manager | Storage | Needs a request? |
|
||||
|---|---|---|
|
||||
| `DefaultOAuth2AuthorizedClientManager` | `OAuth2AuthorizedClientRepository` (session) | **Yes** |
|
||||
| `AuthorizedClientServiceOAuth2AuthorizedClientManager` | `OAuth2AuthorizedClientService` | No |
|
||||
|
||||
For service-to-service calls there is no end user whose authorization is being stored per
|
||||
session, so the second one is right — and it is the one that keeps working from a scheduled
|
||||
task, a message listener or an `@Async` method.
|
||||
|
||||
Getting this wrong produces `ClientAuthorizationRequiredException` or a silent `null` in a
|
||||
context that has no `HttpServletRequest`, which reads like an OAuth problem and is a bean
|
||||
problem.
|
||||
|
||||
## What it caches, and what it does not
|
||||
|
||||
The manager stores the authorized client (access token and, if issued, refresh token) in the
|
||||
`OAuth2AuthorizedClientService` and reuses it until it is within the clock skew of expiry.
|
||||
So a `client_credentials` registration does **not** hit the token endpoint per request. What it
|
||||
does do is re-request on expiry, synchronously, inside whichever call happens to be first —
|
||||
worth knowing when a latency percentile spikes on a period that matches your token lifetime.
|
||||
|
||||
## The hand-rolled relay, and why it is still reasonable
|
||||
|
||||
The relay interceptor in
|
||||
[`DownstreamClients`](../src/main/java/com/ankurm/s2s/edge/DownstreamClients.java) does not use
|
||||
any of the above:
|
||||
|
||||
```java
|
||||
var authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication instanceof JwtAuthenticationToken token) {
|
||||
request.getHeaders().setBearerAuth(token.getToken().getTokenValue());
|
||||
}
|
||||
```
|
||||
|
||||
That is not a worse version of `OAuth2ClientHttpRequestInterceptor`; it is a different thing.
|
||||
The interceptor **obtains** a token under a client registration. This **forwards** the token
|
||||
already in hand. There is no client registration for "the caller's token", and there should not
|
||||
be. Just do not confuse the two: the hand-rolled one carries the `ThreadLocal` dependency from
|
||||
chapter 2, and the framework one does not.
|
||||
|
||||
---
|
||||
*Prev: [2. Three ways to get a token](02-three-ways-to-get-a-token.md) · Next: [4. What a resource server does not validate](04-what-is-not-validated.md)*
|
||||
Reference in New Issue
Block a user