As I know the RestTemplateBuilder is some kind of factory for RestTemplate. I have a few questions about using it:
Very often in examples there is something like this in #Configuration class:
#Bean
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate();
...
return restClient;
}
Shouldn't RestTemplate be instantiated per #Service class ? If so, how to customize it ?
Spring reference says that RestTemplateBuilder should be customized via RestTemplateCustomizer. How to manage many URI's from many IP addresses with one builder ?
How to add BasicAuthentication globaly to all RestTemplates via RestTemplateBuilder, and is it a good practice?
Thanks for help.
UPDATE:
My application calls rest services from many servers at different IP's and urls - so logically for me is the situation when I have many RestTemplates.
I'm trying to have a factory (RestTemplateBuilder) per server - let's say servers A, B, C. I know how to add a basic authentication. But what for example when I want a basic authentication for server A but not for server B ?
I think about having one RestTemplateBuilder per server. I don't want to do this manually - I would prefer to use Spring mechanisms.
Any help ?
No, you don't need to, typically you will have on rest template instance, and you would pass different url, and request parameters accordingly every time.
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, vars);
Foo foo = restTemplate.getForObject(fooResourceUrl + "/1", Foo.class);
A descriptive example from spring doc, you can add as many customizers to the builder
public class ProxyCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
HttpHost proxy = new HttpHost("proxy.example.com");
HttpClient httpClient = HttpClientBuilder.create()
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
#Override
public HttpHost determineProxy(HttpHost target,
HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, request, context);
}
}).build();
restTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(httpClient));
}
}
Any RestTemplateCustomizer beans will be automatically added to the
auto-configured RestTemplateBuilder. Furthermore, a new
RestTemplateBuilder with additional customizers can be created by
calling additionalCustomizers(RestTemplateCustomizer…​)
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
I've set up my config like this:
#Bean
public RestTemplateCustomizer restTemplateCustomizer() {
return restTemplate -> {
restTemplate.setRequestFactory(clientHttpRequestFactory());
};
}
#Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
clientHttpRequestFactory.setConnectTimeout(connectionTimeoutMs);
clientHttpRequestFactory.setReadTimeout(connectionTimeoutMs);
clientHttpRequestFactory.setBufferRequestBody(false);
return clientHttpRequestFactory;
}
Whenever Spring injects a RestTemplateBuilder, it will configure it using this RestTemplateCustomizer to use the ClientHttpRequestFactory. You may need to do some different customizations, or perhaps none in which case don't declare the bean.
To add the authentication header, you will need to know the user name and password, which you probably won't know until run-time. So I've created an Authenticator bean:
#Component
public class Authenticator {
#Autowired
private RestTemplateBuilder restTemplateBuilder;
public void withAuthenticationHeader(String username, String password, Consumer<RestTemplate> doAuthenticated) {
RestTemplate restTemplate =
restTemplateBuilder
.basicAuthorization(username, password)
.build();
try {
doAuthenticated.accept(restTemplate);
} catch (HttpClientErrorException exception) {
// handle the exception
}
}
}
This allows me to handle authentication failures in a standard way for all requests, which is what I need in my application.
It is injected into other beans and used like so:
#Autowired
private Authenticator authenticator;
public void transmit() {
authenticator.withAuthenticationHeader(username, password, restTemplate ->
restTemplate.postForLocation(url, request));
}
So you'd use the Authenticator rather than using the RestTemple directly.
I couldn't find any standard patterns for this sort of thing, but this seems to work.
Related
We have a Spring Boot microservice that does the SOAP call to the external system using org.springframework.ws.client.core.WebServiceTemplate.
Now the system would be protected with Keycloak, so all the request need to beak the auth token.
If it was a REST API, I would just replace the pre-existed RestTemplate with OAuth2RestTemplate. But how to instrument the calls initially done by the org.springframework.ws.client.core.WebServiceTemplate ?
So, I understand, I should put the authentication header manually with value 'Bearer ....token there...'. How I can retrieve that part manually to put it into the request?
you can get current request token using RequestContextHolder class and add into soap request header.
String token = ((ServletRequestAttributes)(RequestContextHolder.getRequestAttributes())).getRequest().getHeader("Authorization");
Also I would suggest use Webservice interecptor instead of adding header in each web service request call.
The problem was caused by
Existing library code, based on org.springframework.ws.client.core.WebServiceTemplate, so large and huge for rewriting it using WebClient, compatible with OAuth2 SpringSecurity or use depricated OAuth2RestTemplate
The webservice we previously communicated with, turns into protected with Gravitee and accepts queries with JWT tokens only. So, the only change here is to add the Authentication header with 'Bearer ....token there...'
We initiate the call from the scheduled jo in the microservice. So, it should be getting token from the Keycloak before the request and be able to update it with time. No one does the explicit authorization like in the frontend, so the OAuth2 client should use client-id and client-secret to connect with no human involved
The Solution
At the beginning, we define the Interceptor to the SOAP calls, that will pass the token as a header, via a Supplier function taking it wherever it can be taken:
public class JwtClientInterceptor implements ClientInterceptor {
private final Supplier<String> jwtToken;
public JwtClientInterceptor(Supplier<String> jwtToken) {
this.jwtToken = jwtToken;
}
#Override
public boolean handleRequest(MessageContext messageContext) {
SoapMessage soapMessage = (SoapMessage) messageContext. getRequest();
SoapHeader soapHeader = soapMessage.getSoapHeader();
soapHeader.addHeaderElement(new QName("authorization"))
.setText(String. format("Bearer %s", jwtToken.get()));
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException {
}
}
Then pass it to the template in addition to other pre-existed interceptor to be called in config class:
protected WebServiceTemplate buildWebServiceTemplate(Jaxb2Marshaller marshaller,
HttpComponentsMessageSender messageSender, String uri, Supplier<String> jwtToken) {
WebServiceTemplate template = new WebServiceTemplate();
template.setMarshaller(marshaller);
template.setUnmarshaller(marshaller);
template.setMessageSender(messageSender);
template.setDefaultUri(uri);
ClientInterceptor[] clientInterceptors = ArrayUtils.addAll(template.getInterceptors(), new Logger(), new JwtClientInterceptor(jwtToken));
template.setInterceptors(clientInterceptors);
return template;
}
Then add the Spring Security Oath2 Client library
compile 'org.springframework.security:spring-security-oauth2-client:5.2.1.RELEASE'
We create OAuth2AuthorizedClientService bean, that uses a standard ClientRegistrationRepository (the repository is initiated through usage of #EnableWebSecurity annotation on the #Configuration class, but please double check about that)
#Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
Then create a OAuth2AuthorizedClientManager
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
Authentication authentication = new Authentication() {
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
GrantedAuthority grantedAuthority = new GrantedAuthority() {
#Override
public String getAuthority() {
return "take_a_needed_value_from_property";
}
};
return Arrays.asList(grantedAuthority);
}
#Override
public Object getCredentials() {
return null;
}
#Override
public Object getDetails() {
return null;
}
#Override
public Object getPrincipal() {
return new Principal() {
#Override
public String getName() {
return "our_client_id_from_properties";
}
};
}
#Override
public boolean isAuthenticated() {
return true;
}
#Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
#Override
public String getName() {
return "take_a_needed_name_from_properties";
}
};
//we need to emulate Principal there, as other classes relies on it. In fact, Principal isn't needed for the app which is a client and just do the call, as nothing is authorized in the app against this Principal itself
OAuth2AuthorizationContext oAuth2AuthorizationContext = OAuth2AuthorizationContext.withClientRegistration(clientRegistrationRepository.findByRegistrationId("keycloak")).
principal(authentication).
build();
oAuth2AuthorizationContext.getPrincipal().setAuthenticated(true);
oAuth2AuthorizationContext.getAuthorizedClient();
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder().
//refreshToken().
clientCredentials(). //- we use this one according to our set up
//authorizationCode().
build();
OAuth2AuthorizedClientService oAuth2AuthorizedClientService = oAuth2AuthorizedClientService(clientRegistrationRepository); //use the bean from before step here
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, oAuth2AuthorizedClientService);
OAuth2AuthorizedClient oAuth2AuthorizedClient = authorizedClientProvider.authorize(oAuth2AuthorizationContext);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
oAuth2AuthorizedClientService.saveAuthorizedClient(oAuth2AuthorizedClient,
oAuth2AuthorizationContext.getPrincipal());
//this step is needed, as without explicit authorize call, the
//oAuth2AuthorizedClient isn't initialized in the service
return authorizedClientManager;
}
Provide a method for supplied function that can be called each time to retrieve the JWT token from the security stuff (repository and manager). Here it should be auto-updated, so we just call for retrieving it
public Supplier<String> getJwtToken() {
return () -> {
OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient("keycloak", "we_havePout_realm_there_from_the_properties");
return authorizedClient.getAccessToken().getTokenValue();
};
}
Pass this Consumer to the #Bean, defining the WebServiceTemplate's
#Bean
public Client client(#Qualifier("Sender1") HttpComponentsMessageSender bnfoMessageSender,
#Qualifier("Sender2") HttpComponentsMessageSender uhMessageSender) {
WebServiceTemplate sender1= buildWebServiceTemplate(buildSender1Marshaller(), sender1MessageSender, properties.getUriSender1(),getJwtToken());
WebServiceTemplate sender2 = buildWebServiceTemplate(buildSender2Marshaller(), sender2MessageSender, properties.getUriSender2(),getJwtToken());
return buildClient(buildRetryTemplate(), sender1, sender2);
}
We add Spring Security Client values to application.yaml in order to configure it.
spring:
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: https://host/keycloak/auth/realms/ourrealm
registration:
keycloak:
client-id: client_id
client-secret: client-secret-here
authorization-grant-type: client_credentials //need to add explicitly, otherwise would try other grant-type by default and never get the token!
client-authentication-method: post //need to have this explicitly, otherwise use basic that doesn't fit best the keycloak set up
scope: openid //if your don't have it, it checks all available scopes on url like https://host/keycloak/auth/realms/ourrealm/ .well-known/openid-configuration keycloak and then sends them as value of parameter named 'scope' in the query for retrieving the token; that works wrong on our keycloak, so to replace this auto-picked value, we place the explicit scopes list here
I have two microservices Microservice A ( context path - /abc ) and microservice B (context path - /def )
Example URLs: test.domain.com/abc/endpoint1 ,test.domain.com/def/endpoint2
In one of the apis of Microservice A ( test.domain.com/abc/endpoint1) internally its making call to Microservice B (/def/endpoint2) -> the prefix for this internal call is generated as follows
(Extract the domain from the request and then append /def/endpoint2 to make a rest call the total url will become as (test.domain.com/def/endpoint2)
Problem : When we are writting unit test cases starting controller level we are using TestRestTemplate
For this testing we need to use http://localhost:portnumber/abc/endpoint1 to test ..
Now the url of the def service also will be derived as http://localhost:portnumber/def/endpoint2
How to mock this response ( Note: We cannot use mock server on same port, we will get port binding exception) . Is there any workaround for the same?
Is there any way to have gateway kind of setup while using TestRestTemplate to route http://localhost:portnumber/def/* calls to get response from mockserver and http://localhost:portnumber/abc/* to make the actual API Service under test?
You could use a ClientHttpRequestInterceptor for this and manipulate the actual URI to call if it matches the path of your second microservice.
This might be a naive protoypish implementation:
public class UrlRewriter implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
try {
if (httpRequest.getURI().toString().contains("/def/abc")) {
HttpRequest modifiedRequest = new MockClientHttpRequest(HttpMethod.GET, new URI("http://localhost:8888/def/abc"));
return clientHttpRequestExecution.execute(modifiedRequest, bytes);
} else {
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
} catch (URISyntaxException e) {
e.printStackTrace();
return null;
}
}
}
And then you can provide a custom bean of type RestTemplateBuilder for your test that is picked up by the TestRestTemplate:
#SpringBootTest(webEnvironment = RANDOM_PORT)
public class TestOne {
#TestConfiguration
static class TestConfig {
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder().interceptors(new UrlRewriter());
}
}
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void test() {
assertNotNull(testRestTemplate);
testRestTemplate.getForObject("/abc/endpoint1", String.class);
}
}
What is the difference in creating RestTemplate this way
RestTemplate restTemplate = restTemplateBuilder
.setConnectTimeout(Duration.ofMillis(connectTimeout))
.setReadTimeout(Duration.ofMillis(readTimeout))
.build();
and this way
CloseableHttpClient httpClient = HttpClientBuilder.create().disableCookieManagement().build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setReadTimeout(readTimeout);
factory.setConnectTimeout(connectTimeout);
RestTemplate restTemplate = new RestTemplate(factory);
????
I think your question about Scope restTemplateBuilder.As mention in Spring Document:
Scope of restTemplateBuilder
To make the scope of any customizations as narrow as possible, inject
the auto-configured RestTemplateBuilder and then call its methods as
required. Each method call returns a new RestTemplateBuilder instance,
so the customizations only affect this use of the builder.
Example:
private RestTemplate restTemplate;
#Autowired
public HelloController(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
To make an application-wide, additive customization, use a
RestTemplateCustomizer bean. All such beans are automatically
registered with the auto-configured RestTemplateBuilder and are
applied to any templates that are built with it.
Example
static class ProxyCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
HttpHost proxy = new HttpHost("proxy.example.com");
HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
#Override
public HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, request, context);
}
}).build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
}
}
Note: For narrow using RestTemplateBuilder. For application-wide using RestTemplateCustomizer
Reference link: Reference link
Additional detail example: Additional example
I'm not able to bind an attribute that I'm setting from a WebTestClient into a RestController when using Spring WebFlux.
I tried the two ways I could think of.
First using the #RequestAttribute annotation and I got:
Failed to handle request [GET /attributes/annotation]: Response status 400 with reason "Missing request attribute 'attribute' of type String"
Then I tried with the ServerWebExchange and was null.
This is my controller:
#RestController
#RequestMapping("/attributes")
public class MyController {
#GetMapping("/annotation")
public Mono<String> getUsingAnnotation(#RequestAttribute("attribute") String attribute) {
return Mono.just(attribute);
}
#GetMapping("/exchange")
public Mono<String> getUsingExchange(ServerWebExchange exchange) {
return Mono.just(exchange.getRequiredAttribute("attribute"));
}
}
And this is my failing test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
ApplicationContext context;
WebTestClient webClient;
#Before
public void setup() {
webClient = WebTestClient.bindToApplicationContext(context)
.configureClient()
.build();
}
#Test
public void testGetAttributeUsingAnnotation() {
webClient.get()
.uri("/attributes/annotation")
.attribute("attribute", "value")
.exchange()
.expectStatus()
.isOk();
}
#Test
public void testGetAttributeUsingExchange() {
webClient.get()
.uri("/attributes/exchange")
.attribute("attribute", "value")
.exchange()
.expectStatus()
.isOk();
}
}
In my real application I have a SecurityContextRepository that sets some attributes from a (decoded) header value and I'd like to get those attributes.
I've run into the same issue with a test which previously used MockMvc and then had to be converted to use WebClient. Like #jcfandino I was expecting the .attribute() methods on the WebClient to work similar to MockMvc's requestAttribute().
I haven't found out how .attribute() is meant to be used but I've bypassed the entire problem by adding a custom test filter. I'm not sure if this approach is correct but since this question has been unanswered the approach below may be of help for people running into the same issue.
#WebFluxTest(controllers = SomeController.class)
#ComponentScan({ "com.path1", "com.path2" })
class SomeControllerTest {
// define a test filter emulating the server's filter (assuming there is one)
private class AttributeFilter implements WebFilter {
String attributeValue;
public AttributeFilter(String filterAttributeValue) {
attributeValue = filterAttributeValue;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// add the desired attributes
exchange.getAttributes().put(SomeController.ATTR_NAME, attributeValue);
return chain.filter(exchange);
}
}
// mock the service the controller is dependend on
#MockBean
AppService appService;
// define the test where the controller handles a get() operation
#Test
void testMethod() {
// mock the app service
when(appService.executeService(anyString(), anyString())).thenAnswer(input -> {
// ... return some dummy appData
});
var testClient= WebTestClient.bindToController(new SomeController(appService))
.webFilter(new SomeControllerTest.AttributeFilter("someValue"))
.build();
try {
var response = testClient
.get()
.uri("someroute")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody(AppData.class);
} catch (Exception e) {
fail("exception caught in testMethod", e);
}
}
}
Both on the server and client side, request attributes should be seen as Map-like data structures that can be used to transfer information within the client/server (for filters, codecs, etc).
That information is not sent over the network.
If you want to send that information from the client to the server, you should take a look at request params or the request body itself.
Using Spring Cloud, I am unable to eliminate thrown exceptions using a custom ResponseErrorHandler on my RestTemplate using the latest releases. A year ago, we implemented something similar to How to Ignore HttpStatus Exceptions. This worked until we ported to spring boot/cloud 1.0.2 and greater. Setting breakpoints in those handlers when timeouts or connectivity issues occur do not get triggered in debug mode when using Spring Cloud 1.0.2 or SR3.
#Configuration
class MyConfig {
:
#Bean
public RestTemplate restTemplate() {
RestTemplate toRet = new RestTemplate(httpRequest());
toRet.setErrorHandler(new ExceptionLessErrorHandler());
return toRet;
}
}
ExceptionLessErrorHandler.java
public class ExceptionLessErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse response) {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
logger.error("handleError called with {}", response);
// do nothing!
}
}
and finally when we call, using same restTemplate (I verified the ErrorHandler was set to ExceptionLessErrorHandler)
#Autowired
private RestTemplate restTemplate;
:
class GetRemoteJsonResponse implements Callable<List<JsonNode>> {
:
#Override
public List<JsonNode> call() {
:
ResponseEntity<String> remoteUsers = restTemplate.getForEntity(URL.toString(), String.class, params);
}
}
an exception is thrown triggered by java.net.SocketTimeoutException: connect timed out. This was not the behavior prior to our port (we were using Spring 3.2.4) prior.
Update. To be specific about the release of spring-cloud-starter-parent
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Angel.SR3</version>
<!--<version>1.0.2</version>-->
<relativePath />
</parent>
I have not tried other spring-cloud-starter-parent releases other than these two.
If the exception is the result of a connection issue, ResponseErrorHandler will not be called as it is only used for response issues. The only way to stop connection issues from throwing an exception, that I'm aware of, is adding some kind of localhost Proxy via HttpConnectionFactory.
Here is what I had to do for the Angel release train:
#Bean
#Qualifier("custom")
public RestTemplate restTemplate(RestTemplateCustomizer customizer) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new ExceptionLessErrorHandler());
customizer.customize(restTemplate); //this brings in ribbon.
return restTemplate;
}
#Autowired
#Qualifier("custom")
RestTemplate rest;
Angel.X creates a RestTemplate and you were getting the auto-created one, not the one you created.
I've also created an issue to make this more user friendly and to document how to do this.