# 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)*