Closed bjansen closed 4 years ago
Hi, I'm afraid it is not possible to replace the RestTemplate
used by Charon. This is becuase it must be a special one which makes retrying request possible. The only things you can configure are timeouts, underlying http client and rest template interceptors, see here
What grant type you need to use the OAuth2RestTemplate
with? If it is a stateless grant type, like client credentials grant type, you can create a custom Charon's interceptor (not the rest template one) and it's configurer. The interceptor would acquire an access token by sending a proper request to authorization server. Next, the interceptor will modify outgoing request headers by setting the 'Authorization' header which will include the access token.
Right, I need client credentials. I thought about using a custom interceptor, but I was hoping I could use something already existing.
What can go wrong if the RestTemplate
is not retry-aware? If I extended OAuth2RestTemplate
to make my own RetryAwareOAuth2RestTemplate
, would you consider adding a way to replace the default template?
Here is an example of how to create a RestTemplate
interceptor, which can acquire access tokens. Maybe you can create a similar one and set it via Charon's configration API.
As for the RetryAwareOAuth2RestTemplate
you can't extend RetryAwareRestTemplate
and OAuth2RestTemplate
at the same time. In Charon's confgiuration API I can only expose a setter for RetryAwareRestTemplate
. I can't expose setter for RestTemplate
because then you could set a RestTemplate
that is not aware of retrying.
BTW OAuth2RestTemplate
is deprecated now
Yes, I've seen that the class is deprecated, I will consider switching to WebFlux instead.
Thanks for the answers and the examples!
I've analyzed the code a bit and find a way to actually allow to set a custom RestTemplate
:)
Thus I must use reflection in the code.
If you are still interested let me know.
If it feels like a hack for you, then maybe it's better if I use an interceptor.
On the other hand, if it integrates nicely with the existing configuration DSL, and if you can release this feature "soon enough", then yes I'm definitely interested :)
I think I can implement this, it will nicely integrate with the DSL. Give me up to two days to release it.
Thanks a lot!
Sorry, but I can't implement it like thought.
I can't provide a similar functionality to webflux module (WebClient
has too restricted API) and I want both modules to provide analogical features.
Please try to create a custom interceptor.
All right. For the record, here is the interceptor I made, in case someone else needs it:
import com.github.mkopylec.charon.forwarding.interceptors.RequestForwardingInterceptorConfigurer;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
public class OAuth2HeaderConfigurer extends RequestForwardingInterceptorConfigurer<OAuth2Header> {
private OAuth2HeaderConfigurer() {
super(new OAuth2Header());
}
public static OAuth2HeaderConfigurer oauth2Header() {
return new OAuth2HeaderConfigurer();
}
public OAuth2HeaderConfigurer clientCredentials(String tokenUrl, String clientId, String clientSecret) {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setAccessTokenUri(tokenUrl);
resourceDetails.setClientId(clientId);
resourceDetails.setClientSecret(clientSecret);
configuredObject.setResourceDetails(resourceDetails);
return this;
}
}
import com.github.mkopylec.charon.forwarding.interceptors.HttpRequest;
import com.github.mkopylec.charon.forwarding.interceptors.HttpRequestExecution;
import com.github.mkopylec.charon.forwarding.interceptors.HttpResponse;
import com.github.mkopylec.charon.forwarding.interceptors.RequestForwardingInterceptor;
import com.github.mkopylec.charon.forwarding.interceptors.RequestForwardingInterceptorType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.DefaultOAuth2RequestAuthenticator;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.util.StringUtils;
import static org.springframework.util.Assert.notNull;
public class OAuth2Header implements RequestForwardingInterceptor {
private OAuth2RestTemplate restTemplate;
@Override
public HttpResponse forward(HttpRequest request, HttpRequestExecution execution) {
OAuth2AccessToken accessToken = restTemplate.getAccessToken();
authenticate(request);
return execution.execute(request);
}
/**
* Copied from {@link DefaultOAuth2RequestAuthenticator#authenticate(org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails, org.springframework.security.oauth2.client.OAuth2ClientContext, org.springframework.http.client.ClientHttpRequest)}
* and adapted to support {@link HttpRequest} instead of {@link ClientHttpRequest}.
*/
private void authenticate(HttpRequest request) {
OAuth2ClientContext clientContext = restTemplate.getOAuth2ClientContext();
OAuth2AccessToken accessToken = clientContext.getAccessToken();
if (accessToken == null) {
throw new AccessTokenRequiredException(restTemplate.getResource());
}
String tokenType = accessToken.getTokenType();
if (!StringUtils.hasText(tokenType)) {
tokenType = OAuth2AccessToken.BEARER_TYPE; // we'll assume basic bearer token type if none is specified.
} else if (tokenType.equalsIgnoreCase(OAuth2AccessToken.BEARER_TYPE)) {
// gh-1346
tokenType = OAuth2AccessToken.BEARER_TYPE; // Ensure we use the correct syntax for the "Bearer" authentication scheme
}
request.getHeaders().set("Authorization", String.format("%s %s", tokenType, accessToken.getValue()));
}
@Override
public RequestForwardingInterceptorType getType() {
return new RequestForwardingInterceptorType(550);
}
public void setResourceDetails(OAuth2ProtectedResourceDetails resourceDetails) {
restTemplate = new OAuth2RestTemplate(resourceDetails, new DefaultOAuth2ClientContext());
}
@Override
public void validate() {
notNull(restTemplate, "No OAuth2ProtectedResourceDetails set");
}
}
Thanks for showing the code example to others :)
Hi,
I would like to provide my own instance of
RestTemplate
to aRequestMappingConfigurer
. Specifically, I would like to use aorg.springframework.security.oauth2.client.OAuth2RestTemplate
instead of the defaultRetryAwareRestTemplate
. I can't find a way to do that with the current version, because of private constructors and package-private methods inRestTemplateConfigurer
.Can you think of a way to achieve this?