How can I disable Spring RestTemplate following a HTTP redirect response? - spring

I'm integrating with an ancient service that is adds jsessionid to the URL and redirects to it. I'm using RestTemplate to talk to the service.
The problem is that it follows the redirect forever, since I'm not setting the jsession cookie.
How do I turn off following the redirects in Spring RestTemplate?

I figured out one way to do it, don't know if this is the preferred way to do it.
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
final HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
CloseableHttpClient build =
HttpClientBuilder.create().disableRedirectHandling().build();
factory.setHttpClient(build);
restTemplate.setRequestFactory(factory);
return restTemplate;
}

You can also write your own HttpRequestFactory class and override the default behaviour:
class CustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
super.prepareConnection(connection, httpMethod);
connection.setInstanceFollowRedirects(false);
}
}
#Configuration
public class AppConfig {
#Bean
public RestTemplate httpClient(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(10))
.setReadTimeout(Duration.ofSeconds(10))
.requestFactory(CustomClientHttpRequestFactory.class)
.build();
}
}

Adding my solution as I found the others above not fitting the current Spring 5.x and Spring Boot 2.7.
The important aspect is that Spring can be configured with around 6 different HTTP client libraries. The way to configure them differs, and is done by using the client lib's API directly.
Below is an example for Apache HttpClient (historically called HttpCompoments) used to create a RestTemplate for a test. The syntax is Kotlin.
import org.apache.http.client.HttpClient
import org.apache.http.impl.client.HttpClients
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
import org.springframework.web.client.RestTemplate
#Bean #Qualifier("forTests")
fun restTemplateHttpsWithTestTrustStore(builder: RestTemplateBuilder): RestTemplate {
val httpClient: HttpClient = HttpClients.custom()
.disableRedirectHandling()
.build()
return builder.requestFactory { HttpComponentsClientHttpRequestFactory(httpClient) }.build()
}
And then the test:
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
)
class HttpsWorksTest {
#LocalServerPort private var port = 0
#Autowired #Qualifier("forTests") private lateinit var restTemplateTrustStore: RestTemplate
#Test
fun httpsSelfRequestTest() {
var url = "$proto://localhost:$port/someRediirectingEndpoint"
log.info("Requesting: GET $url")
var response = restTemplateTrustStore.getForEntity(url, String::class.java)
Assertions.assertThat(response.statusCode).isEqualTo(HttpStatus.SEE_OTHER)
}
}

Related

Spring boot oauth2 client credentials dynamically configure with webflux

I'm using spring boot 2.3.6.RELEASE version.
I need to configure my application for oauth2 client credentials with dynamic configuration with webflux integration.
I have tried following code.
#Configuration
public class Oauth2ClientConfig {
#Bean
ReactiveClientRegistrationRepository getRegistration() {
ClientRegistration registration = ClientRegistration
.withRegistrationId("custom")
.tokenUri(env.getProperty("accessTokenUri"))
.clientId(env.getProperty("clientID"))
.clientSecret(env.getProperty("clientSecret"))
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope(env.getProperty("scope"))
.build();
return new InMemoryReactiveClientRegistrationRepository(registration);
}
#Bean(name = "custom")
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
oauth.setDefaultClientRegistrationId("custom");
return WebClient.builder()
.filter(oauth)
.build();
}
}
but in this spring boot version UnAuthenticatedServerOAuth2AuthorizedClientRepository is depricated.
as per the spring documentation it says to use AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager insted of UnAuthenticatedServerOAuth2AuthorizedClientRepository but I couldn't find any proper sample for this implementation. If anyone has an idea about how to implement this configuration please help.
I found the solution my own for depricated UnAuthenticatedServerOAuth2AuthorizedClientRepository.
Spring doc says to use AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager insted of UnAuthenticatedServerOAuth2AuthorizedClientRepository. you can find it here.
here is the complete sample for configuring webflux with Oauth2 in latest spring boot version.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.oauth2.client.AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.web.reactive.function.client.WebClient;
#Configuration
public class Oauth2WebClientConfig {
private final Environment env;
#Autowired
public Oauth2WebClientConfig(Environment env) {
this.env = env;
}
// == Oauth2 Configuration ==
#Bean
ReactiveClientRegistrationRepository clientRegistration() {
ClientRegistration clientRegistration = ClientRegistration
.withRegistrationId("custom")
.tokenUri("tokenUri")
.clientId("clientId")
.clientSecret("clientSecret")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("scope")
.build();
return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
}
#Bean
ReactiveOAuth2AuthorizedClientService authorizedClientService() {
return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistration());
}
// == Oauth2 Configuration ==
// == WebFlux Configuration ==
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations, ReactiveOAuth2AuthorizedClientService authorizedClientService) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, authorizedClientService));
oauth.setDefaultClientRegistrationId("custom");
return WebClient.builder()
.filter(oauth)
.build();
}
// == WebFlux Configuration ==
}

Spring TestRestTemplate timeout

When debugging a Spring integration test which uses TestRestTemplate (Note: NOT RestTemplate), I sometimes find the client side of the test times out if I'm stepping through breakpoints on the production code (server side).
How do I change the timeouts for Spring TestRestTemplate?
Add a new config class at the top of your test (I called it TestConfig):
#SpringBootTest(webEnvironment = ..., classes = [YourApplication, TestConfig])
then include that TestConfig class
and include a custom RestTemplateBuilder as follows
These can both be inner classes of your test class if needed
public static class TestConfig {
#Bean
public MyRestTemplateBuilder myRestTemplateBuilder() {
return new MyRestTemplateBuilder()
}
}
public static class MyRestTemplateBuilder extends RestTemplateBuilder {
MyRestTemplateBuilder(RestTemplateCustomizer... customizers) {
super(customizers)
}
#Override
ClientHttpRequestFactory buildRequestFactory() {
ClientHttpRequestFactory result = super.buildRequestFactory()
// Usually result is a: org.springframework.http.client.HttpComponentsClientHttpRequestFactory
result.setConnectTimeout(100000);
result.setReadTimeout(100000);
return result
}
}
Your timeouts will be modified as required.
Bonus:
For normal RestTemplate timeouts you can do:
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// This code can be used to change the read timeout for testing
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
simpleClientHttpRequestFactory.setReadTimeout(100); // millis
return restTemplate;
}

Multiple rest templates in spring boot

I want to configure multiple rest template clients to access different API's. Both are having different authorization headers. I already configured one, Same way configured other rest template too, but that throws error bean 'restTemplate' defined in class path resource .class could not be registered..
#Configuration
public class RestTemplateConfig {
#Autowired
private HeaderRequestInterceptor headerRequestInterceptor;
//constructor
public RestClientConfig() {}
#Bean
public RestTemplate restTemplate( RestTemplateBuilder builder ) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(headerRequestInterceptor));
return restTemplate;
}
}
HeaderRequestInterceptor has base64 encoded authorization, so could not post that code here.
Another RestTemplate:
#Configuration
public class AnotherRestClientConfig {
#Autowired
private AnotherHeaderRequestInterceptor anotherHeaderRequestInterceptor;
#Bean
public RestTemplate restTemplate( RestTemplateBuilder builder ) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(anotherHeaderRequestInterceptor));
return restTemplate;
}
}
Could someone let me know how to configure multiple rest templates in an application.
you could use #Qualifier as mentioned by #VirtualTroll. Or create a specific client bean per api and hold the restemplate instance there.
#Component
public class ApiClient1 {
private final RestTemplate customRestTemplate;
public ApiClient1() {
this.customRestTemplate = ...
}
public void useApi() {
}
}

How to add SAML token to CXF client request in Spring Boot

We're building a CXF client in Spring Boot. The SAML token to authenticate/authorize against the SOAP server is provided to our app in custom HTTP header from an external auth proxy with every request. Hence, I need a way to add the provided token to every outgoing CXF request.
I know that I could possibly register a custom CXF out interceptor for that. However,
How would I go about registering that interceptor in Spring Boot?
If not done with an interceptor what would be the alternatives?
Currently, the Spring config looks like this:
#Configuration
public class MyConfig {
#Bean
public PartnerServicePortType partnerServicePortType() {
PartnerServicePortType partnerServicePortType = new PartnerServiceV0().getPartnerService();
(PartnerServiceV0 is generated from the service's WSDL with Maven.)
In the above config class we don't currently declare/configure a CXF bus bean.
One possible solution is this:
#Configuration
public class MyConfig {
#Bean
public PartnerServicePortType partnerServicePortType() {
PartnerServicePortType service = new PartnerServiceV0().getPartnerService();
configure(service, path, baseUrl);
return service;
}
private void configureService(BindingProvider bindingProvider, String path, String baseUrl) {
// maybe try the approach outlined at https://github
// .com/kprasad99/kp-soap-ws-client/blob/master/src/main/java/com/kp/swasthik/soap/CxfConfig.java#L24
// as an alternative
bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, baseUrl + path);
Endpoint cxfEndpoint = ClientProxy.getClient(bindingProvider).getEndpoint();
cxfEndpoint.getInInterceptors().add(cxfLoggingInInterceptor);
cxfEndpoint.getInFaultInterceptors().add(cxfLoggingInInterceptor);
cxfEndpoint.getOutInterceptors().add(addSamlAssertionInterceptor);
}
}
And the interceptor
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.binding.soap.SoapHeader;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.XMLObjectBuilder;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.soap.wssecurity.Created;
import org.opensaml.soap.wssecurity.Expires;
import org.opensaml.soap.wssecurity.Security;
import org.opensaml.soap.wssecurity.Timestamp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
/**
* Adding SOAP header with SAML assertion to request.
*/
#Slf4j
#Component
public class AddSamlAssertionInterceptor extends AbstractSoapInterceptor {
private final SamlAssertionExtractor samlAssertionExtractor;
#Autowired
public AddSamlAssertionInterceptor(SamlAssertionExtractor samlAssertionExtractor) {
super(Phase.POST_LOGICAL);
this.samlAssertionExtractor = samlAssertionExtractor;
}
#Override
public void handleMessage(SoapMessage message) throws Fault {
String decodedToken = SamlTokenHolder.getDecodedToken();
if (StringUtils.isBlank(decodedToken)) {
log.trace("Not adding SOAP header with SAML assertion because SAML token is blank.");
} else {
log.trace("Got decoded SAML token: {}", decodedToken);
log.trace("Adding SOAP header with SAML assertion to request.");
SoapHeader header = createSoapHeaderWithSamlAssertionFrom(decodedToken);
message.getHeaders().add(header);
}
}
private SoapHeader createSoapHeaderWithSamlAssertionFrom(String decodedToken) {
Assertion assertion = samlAssertionExtractor.extractAssertion(decodedToken);
Security security = createNewSecurityObject();
security.getUnknownXMLObjects().add(createTimestampElementFrom(assertion));
security.getUnknownXMLObjects().add(assertion);
log.trace("Creating new SOAP header with WS-Security element for '{}'.",
assertion.getSubject().getNameID().getValue());
SoapHeader header = new SoapHeader(security.getElementQName(), marshallToDom(security));
header.setMustUnderstand(config.isMustUnderstandHeader());
return header;
}
#SneakyThrows(MarshallingException.class)
private Element marshallToDom(Security security) {
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(security);
return marshaller.marshall(security);
}
/*
* SAML requirements documented at https://docs.oasis-open.org/wss/v1.1/wss-v1.1-spec-errata-os-SOAPMessageSecurity
* .htm#_Toc118717167. Both timestamps must be in UTC and formatted to comply with xsd:dateTime.
*/
private Timestamp createTimestampElementFrom(Assertion assertion) {
Timestamp timestamp = (Timestamp) createOpenSamlXmlObject(Timestamp.ELEMENT_NAME);
Created created = (Created) createOpenSamlXmlObject(Created.ELEMENT_NAME);
Expires expires = (Expires) createOpenSamlXmlObject(Expires.ELEMENT_NAME);
// alternative would be to use timestamp from assertion like so assertion.getConditions().getNotBefore()
created.setValue(ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
// security semantics should ensure that the expiry date here is the same as the expiry of the SAML assertion
expires.setValue(assertion.getConditions().getNotOnOrAfter().toString());
timestamp.setCreated(created);
timestamp.setExpires(expires);
return timestamp;
}
private Security createNewSecurityObject() {
return (Security) createOpenSamlXmlObject(Security.ELEMENT_NAME);
}
private XMLObject createOpenSamlXmlObject(QName elementName) {
XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
XMLObjectBuilder<Security> builder = (XMLObjectBuilder<Security>) builderFactory.getBuilder(elementName);
return builder.buildObject(elementName);
}
}

How to Create or configure Rest Template using #Bean in Spring Boot

I want to define RestTemplate as an application bean using #Bean annotation in my configuration class in a spring boot application.
I am calling 4 rest services in different places in my application flow. Currently I am creating RestTemplate every time every request. Is there a way I can define that as application bean using #Bean and inject that using #Autowired?
Main reason for this question is I can able to define RestTemplate using #Bean but when I inject it with #Autowired I am loosing all defined interceptors (Interceptors are not getting called.)
Configuration Class
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new RestServiceLoggingInterceptor());
restClient.setInterceptors(interceptors);
return restClient;
}
Service Class
public class MyServiceClass {
#Autowired
private RestTemplate appRestClient;
public String callRestService() {
// create uri, method response objects
String restResp = appRestClient.getForObject(uri, method, response);
// do something with the restResp
// return String
}
}
It seems my Interceptors are not getting called at all with this configuration. But RestTemplate is able to make a call to the REST service and get a response.
Answer for Spring boot 2.*.* version.
I am using Spring boot 2.1.2.RELEASE and I also added RestTemplate in my project in a class where mail method exists.
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.setConnectTimeout(Duration.ofMillis(300000))
.setReadTimeout(Duration.ofMillis(300000)).build();
}
and Used in my service or other classes like this
#Autowired
RestTemplate res;
and in methods
HttpEntity<String> entity = new HttpEntity<>(str, headers);
return res.exchange(url, HttpMethod.POST, entity, Object.class);
Judging form the name of the interceptor, I'm guessing you're doing some logging in it? You could of missed logging level configuration. I created a small application to check weather your configuration works, using 1.3.6.RELEASE version.
In this class I define the RestTemplate bean and the interceptor with logging.
package com.example;
// imports...
#SpringBootApplication
public class TestApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(TestApplication.class);
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
// Add one interceptor like in your example, except using anonymous class.
restClient.setInterceptors(Collections.singletonList((request, body, execution) -> {
LOGGER.debug("Intercepting...");
return execution.execute(request, body);
}));
return restClient;
}
}
For logging to work, I also have to set the correct debug level in application.properties.
logging.level.com.example=DEBUG
Then I create a service where I inject this RestTemplate.
#Service
public class SomeService {
private final RestTemplate appRestClient;
#Autowired
public SomeService(#Qualifier("appRestClient") RestTemplate appRestClient) {
this.appRestClient = appRestClient;
}
public String callRestService() {
return appRestClient.getForObject("http://localhost:8080", String.class);
}
}
And also an endpoint to test this out.
#RestController
public class SomeController {
private final SomeService service;
#Autowired
public SomeController(SomeService service) {
this.service = service;
}
#RequestMapping(value = "/", method = RequestMethod.GET)
public String testEndpoint() {
return "hello!";
}
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
return service.callRestService();
}
}
By performing a GET request to http://localhost:8080/test I should expect to get the String hello! getting printed (the service makes a call to http://localhost:8080 which returns hello! and sends this back to me). The interceptor with logger also prints out Intercepting... in the console.
Edd's solution won't work if you're using Spring Boot 1.4.0 or later. You will have to use RestTemplateBuilder to get this working. Here is the example
#Bean(name="simpleRestTemplate")
#Primary
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
RestTemplate template = restTemplateBuilder.requestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
.interceptors(logRestRequestInterceptor) //This is your custom interceptor bean
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
return template;
}
Now you can autowire the bean into your service class
#Autowired
#Qualifier("simpleRestTemplate")
private RestTemplate simpleRestTemplate;
Hope this helps

Resources