3.5 KiB
3. RestClient interceptors
Prev: 2. Three ways to get a token · Next: 4. What a resource server does not validate
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:
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:
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 does not use
any of the above:
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 · Next: 4. What a resource server does not validate