Spring RestTemplate and HttpClient connection pooling limit - spring

I'm using RestTemplate on my jsf web server, and with these configuration I often reach limit of connection to routes so soon (exp:/ap-domain/get-all).
How can I release connection to avoid limit?
This is my config class:
#Configuration
public class RestTemplateConfig {
#Bean
public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
PoolingHttpClientConnectionManager result = new PoolingHttpClientConnectionManager();
result.setMaxTotal(100);
result.setDefaultMaxPerRoute(20);
return result;
}
#Bean
public CloseableHttpClient httpClient() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
CloseableHttpClient result = HttpClientBuilder.create()
.setConnectionManager(poolingHttpClientConnectionManager())
.setDefaultRequestConfig(requestConfig())
.setSSLSocketFactory(csf)
.build();
return result;
}
#Bean
public RestTemplate restTemplate() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setErrorHandler(new RestResponseErrorHandler());
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (interceptors.isEmpty()) {
interceptors = new ArrayList<>();
}
interceptors.add(new RestTemplateHeaderInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
My RestUtil to call api:
#Component
public class RestUtil {
private static RestTemplate restTemplate;
#Autowired
public RestUtil(RestTemplate restTemplate) {
RestUtil.restTemplate = restTemplate;
}
public static <T> MessagesResponse<T> exchange(String url, HttpMethod method, Object requestObject, String token, ParameterizedTypeReference<MessagesResponse<T>> type) {
...
}
}

Related

JwtAuthenticationToken is not in the allowlist, Jackson issue

I have created my authorization server using org.springframework.security:spring-security-oauth2-authorization-server:0.2.2 and my client using org.springframework.boot:spring-boot-starter-oauth2-client. The users are able to sign in and out successfully, however, while testing I noticed that if I log in successfully then restart the client (but not the server) without signing out and try to login in again the server throws the following error in an endless loop of redirects
java.lang.IllegalArgumentException: The class with org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken and name of org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details
I tried to follow this link https://github.com/spring-projects/spring-security/issues/4370 but the solution on it did not work for me. I also tried a different solution described in this link https://github.com/spring-projects/spring-authorization-server/issues/397#issuecomment-900148920 and modified my authorization server code as follows:-
Here is my Jackson Configs
#Configuration
public class JacksonConfiguration {
/**
* Support for Java date and time API.
*
* #return the corresponding Jackson module.
*/
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
#Bean
public Jdk8Module jdk8TimeModule() {
return new Jdk8Module();
}
/*
* Support for Hibernate types in Jackson.
*/
#Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
/*
* Module for serialization/deserialization of RFC7807 Problem.
*/
#Bean
public ProblemModule problemModule() {
return new ProblemModule();
}
/*
* Module for serialization/deserialization of ConstraintViolationProblem.
*/
#Bean
public ConstraintViolationProblemModule constraintViolationProblemModule() {
return new ConstraintViolationProblemModule();
}
/**
* To (de)serialize a BadCredentialsException, use CoreJackson2Module:
*/
#Bean
public CoreJackson2Module coreJackson2Module() {
return new CoreJackson2Module();
}
#Bean
#Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(coreJackson2Module());
mapper.registerModule(javaTimeModule());
mapper.registerModule(jdk8TimeModule());
mapper.registerModule(hibernate5Module());
mapper.registerModule(problemModule());
mapper.registerModule(constraintViolationProblemModule());
return mapper;
}
}
and here is my Authorization server config
#Configuration(proxyBeanMethods = false)
public class AuthServerConfig {
private final DataSource dataSource;
private final AuthProperties authProps;
private final PasswordEncoder encoder;
public AuthServerConfig(DataSource dataSource, AuthProperties authProps, PasswordEncoder encoder) {
this.dataSource = dataSource;
this.authProps = authProps;
this.encoder = encoder;
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
authorizationServerConfigurer.tokenRevocationEndpoint(tokenRevocationEndpoint -> tokenRevocationEndpoint
.revocationResponseHandler((request, response, authentication) -> {
Assert.notNull(request, "HttpServletRequest required");
HttpSession session = request.getSession(false);
if (!Objects.isNull(session)) {
session.removeAttribute("SPRING_SECURITY_CONTEXT");
session.invalidate();
}
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
response.setStatus(HttpStatus.OK.value());
})
);
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http
.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.apply(authorizationServerConfigurer);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, TokenSettings tokenSettings) {
JdbcRegisteredClientRepository clientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
RegisteredClient webClient = RegisteredClient.withId("98a9104c-a9c7-4d7c-ad03-ec61bcfeab36")
.clientId(authProps.getClientId())
.clientName(authProps.getClientName())
.clientSecret(encoder.encode(authProps.getClientSecret()))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8000/login/oauth2/code/web-client")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.tokenSettings(tokenSettings)
.build();
clientRepository.save(webClient);
return clientRepository;
}
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository,
ObjectMapper objectMapper) {
JdbcOAuth2AuthorizationService authorizationService =
new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
objectMapper.registerModules(SecurityJackson2Modules.getModules(classLoader));
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
// You will need to write the Mixin for your class so Jackson can marshall it.
// objectMapper.addMixIn(UserPrincipal .class, UserPrincipalMixin.class);
rowMapper.setObjectMapper(objectMapper);
authorizationService.setAuthorizationRowMapper(rowMapper);
return authorizationService;
}
#Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
#Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer(authProps.getIssuerUri())
.build();
}
#Bean
public TokenSettings tokenSettings() {
return TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofDays(1))
.refreshTokenTimeToLive(Duration.ofDays(1))
.build();
}
}
But am still facing the same issue.
How do I solve this? Any assistance is highly appreciated.
After trying out different solutions this was how I was able to solve it.
I changed my OAuth2AuthorizationService bean to look like this.
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService =
new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper =
new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper oAuth2AuthorizationParametersMapper =
new JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper();
ObjectMapper objectMapper = new ObjectMapper();
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
objectMapper.registerModules(securityModules);
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
objectMapper.addMixIn(JwtAuthenticationToken.class, JwtAuthenticationTokenMixin.class);
rowMapper.setObjectMapper(objectMapper);
oAuth2AuthorizationParametersMapper.setObjectMapper(objectMapper);
authorizationService.setAuthorizationRowMapper(rowMapper);
authorizationService.setAuthorizationParametersMapper(oAuth2AuthorizationParametersMapper);
return authorizationService;
}
and here is my JwtAuthenticationTokenMixin configurations
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
#JsonDeserialize(using = JwtAuthenticationTokenDeserializer.class)
#JsonAutoDetect(
fieldVisibility = JsonAutoDetect.Visibility.ANY,
getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class JwtAuthenticationTokenMixin {}
class JwtAuthenticationTokenDeserializer extends JsonDeserializer<JwtAuthenticationToken> {
#Override
public JwtAuthenticationToken deserialize(JsonParser parser, DeserializationContext context) throws IOException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
JsonNode root = mapper.readTree(parser);
return deserialize(parser, mapper, root);
}
private JwtAuthenticationToken deserialize(JsonParser parser, ObjectMapper mapper, JsonNode root)
throws JsonParseException {
JsonNode principal = JsonNodeUtils.findObjectNode(root, "principal");
if (!Objects.isNull(principal)) {
String tokenValue = principal.get("tokenValue").textValue();
long issuedAt = principal.get("issuedAt").longValue();
long expiresAt = principal.get("expiresAt").longValue();
Map<String, Object> headers = JsonNodeUtils.findValue(
principal, "headers", JsonNodeUtils.STRING_OBJECT_MAP, mapper);
Map<String, Object> claims = new HashMap<>();
claims.put("claims", principal.get("claims"));
Jwt jwt = new Jwt(tokenValue, Instant.ofEpochMilli(issuedAt), Instant.ofEpochMilli(expiresAt), headers, claims);
return new JwtAuthenticationToken(jwt);
}
return null;
}
}
abstract class JsonNodeUtils {
static final TypeReference<Set<String>> STRING_SET = new TypeReference<Set<String>>() {
};
static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<Map<String, Object>>() {
};
static String findStringValue(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isTextual()) ? value.asText() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
ObjectMapper mapper) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null;
}
static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isObject()) ? value : null;
}
}
you don't need to create a Mixin, because it's all ready created by authorization springboot module. juste
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModules(new CoreJackson2Module());
objectMapper.registerModules(SecurityJackson2Modules.getModules(classLoader));
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
rowMapper.setObjectMapper(objectMapper);
authorizationService.setAuthorizationRowMapper(rowMapper);
return authorizationService;
}
i think you miss this line and is where the token mixin is registered
objectMapper.registerModules(new CoreJackson2Module());

Autowired RestTemplate returns incorrect ResponseErrorHandler

everyone. I am now working in a Spring Boot project that one of the function is to send some HTTP requests to third party API and get the responses.
I decided to use RestTemplate as the http client and I created a bean for RestTemplate in a java file with #Configuration
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new RequestLoggingInterceptor());
restTemplate.setInterceptors(interceptors);
restTemplate.setErrorHandler(new RestResponseErrorHandler());
return restTemplate;
}
Custom error handler and logging interceptor are set in restTemplate. Then, I injected the RestTemplate into my service class by
#Autowired
RestTemplate restTemplate;
However, I found that my custom error handler is not working properly and I tried to check the error handler by
ResponseErrorHandler customErrorHandler = restTemplate.getErrorHandler();
I found that theErrorHandler is still DefaultResponseErrorHandler but not my custom errorHandler. Why? Is there anything wrong?
Updated
Below are my custom error handler and logging interceptor
public class RestResponseErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
if (!response.getStatusCode().equals(HttpStatus.ACCEPTED)) {
return true;
}
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
if (!response.getStatusCode().equals(HttpStatus.ACCEPTED)) {
throw new RestResponseErrorException(response.getStatusCode() + "" + response.getStatusText());
}
}
}
and
public class RequestLoggingInterceptor implements ClientHttpRequestInterceptor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
logRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
logResponse(response);
return response;
}
private void logRequest(HttpRequest request, byte[] body) throws IOException {
if (log.isDebugEnabled()) {
log.debug("Request begin -----------------------------");
log.debug("URI:{}", request.getURI());
log.debug("Method:{}", request.getMethod());
log.debug("Headers:{}", request.getHeaders().toString());
log.debug("Request body:{}", new String(body, "UTF-8"));
log.debug("Request end -----------------------------");
}
}
private void logResponse(ClientHttpResponse response) throws IOException {
if (log.isDebugEnabled()) {
log.debug("Response begin -----------------------------");
log.debug("Status code:{}", response.getStatusCode());
log.debug("Status text:{}", response.getStatusText());
log.debug("Headers:{}", response.getHeaders().toString());
log.debug("Response body:{}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()));
log.debug("Response end -----------------------------");
}
}
}

Enabling Cookies and Redirects in TestRestTemplate

How to I add TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS and TestRestTemplate.HttpClientOption.ENABLE_COOKIES to spring-boots TestRestTemplate?
I am using spring-boot so have a TestRestTemplate configured for me automatically.
I can customise this bean before creation using RestTemplateBuilder. The problem is I can't see how to add these options:
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.errorHandler(new ResponseErrorHandler() {
...
});
}
The documentation has some constructors that accept these options but the problem is the bean has already been created for me.
You can create a TestRestTemplate and present it to Spring by using the #Bean annotation.
For example:
#Bean
#Primary
public TestRestTemplate testRestTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder()
.errorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}).build();
return new TestRestTemplate(restTemplate, user, password, TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS, TestRestTemplate.HttpClientOption.ENABLE_COOKIES);
}
Or, if you do not need to customise the RestTemplate then use the following constructor (which internally instances a RestTemplate for you):
#Bean
#Primary
public TestRestTemplate testRestTemplate() {
return new TestRestTemplate(TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS, TestRestTemplate.HttpClientOption.ENABLE_COOKIES);
}
Update 1 to address this comment:
when I run my tests, I now get the following error org.apache.http.ProtocolException: Target host is not specified
The TestRestTemplate provided by Spring is configured to resolve paths relative to http://localhost:${local.server.port}. So, when you replace the Spring provided instance with your own instance you'll either have to provide the full address (including host and port) or configure your own TestRestTemplate with a LocalHostUriTemplateHandler (you can see this code in org.springframework.boot.test.context.SpringBootTestContextCustomizer.TestRestTemplateFactory). Here's an example of the latter approach:
#Bean
#Primary
public TestRestTemplate testRestTemplate(ApplicationContext applicationContext) {
RestTemplate restTemplate = new RestTemplateBuilder()
.errorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}).build();
TestRestTemplate testRestTemplate =
new TestRestTemplate(restTemplate, user, password, TestRestTemplate.HttpClientOption
.ENABLE_REDIRECTS, TestRestTemplate.HttpClientOption.ENABLE_COOKIES);
// let this testRestTemplate resolve paths relative to http://localhost:${local.server.port}
LocalHostUriTemplateHandler handler =
new LocalHostUriTemplateHandler(applicationContext.getEnvironment(), "http");
testRestTemplate.setUriTemplateHandler(handler);
return testRestTemplate;
}
With this bean configuration the following test case uses the customised TestRestTemplate and successfully invokes the Spring Boot app on localhost:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RestTemplateTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void test() {
ResponseEntity<String> forEntity = this.restTemplate.getForEntity("/some/endpoint", String.class);
System.out.println(forEntity.getBody());
}
}

Exposing OAuth2RestTemplate as AsyncRestTemplate

I have a working OAuth2RestTemplate client (I'm using spring-security-oauth2 2.0.7.RELEASE). Now I'd like to expose/wrap it as AsyncRestTemplate to take advantage of asynchronous semantic of ListenableFuture. Unfortunately the following straightforward approach doesn't work:
// instantiate and configure OAuth2RestTemplate - works
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(...);
// wrap sync restTemplate with AsyncRestTemplate - doesn't work
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(
new HttpComponentsAsyncClientHttpRequestFactory(), oAuth2RestTemplate);
How can I get OAuth2 Rest client for my HTTP service as AsyncRestTemplate?
Ok, I was able to make AsyncRestTemplate work by manually setting "Authorization" header with accessToken from OAuth2RestTemplate; here's Spring java configuration for that:
#Bean
public OAuth2RestTemplate restTemplate() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
// configure oauth details
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(details);
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
return restTemplate;
}
#Bean
public AsyncRestTemplate asyncRestTemplate(final OAuth2RestTemplate oAuth2RestTemplate) {
HttpComponentsAsyncClientHttpRequestFactory asyncRequestFactory = new HttpComponentsAsyncClientHttpRequestFactory() {
#Override
public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException {
AsyncClientHttpRequest asyncRequest = super.createAsyncRequest(uri, httpMethod);
OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken();
asyncRequest.getHeaders().set("Authorization", String.format("%s %s", accessToken.getTokenType(), accessToken.getValue()));
return asyncRequest;
}
};
return new AsyncRestTemplate(asyncRequestFactory, oAuth2RestTemplate);
}
I wish there would be easier way to expose configured OAuth2RestTemplate as AsyncRestTemplate in Spring.
The above works, but I've found a much neater way of doing it.
Register an implementation AsyncClientHttpRequestInterceptor
Example code:
private class Oauth2RequestInterceptor implements AsyncClientHttpRequestInterceptor
{
private final OAuth2RestTemplate oAuth2RestTemplate;
public Oauth2RequestInterceptor( OAuth2RestTemplate oAuth2RestTemplate )
{
this.oAuth2RestTemplate = oAuth2RestTemplate;
}
public ListenableFuture<ClientHttpResponse> intercept( HttpRequest request, byte[] body,
AsyncClientHttpRequestExecution execution ) throws IOException
{
OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken();
request.getHeaders()
.set( "Authorization", String.format( "%s %s", accessToken.getTokenType(), accessToken.getValue() ) );
return execution.executeAsync( request, body );
}
}
Then register it with your AsyncRestTemplate:
#Bean
public AsyncRestTemplate asyncRestTemplate( AsyncClientHttpRequestFactory factory, OAuth2RestTemplate restTemplate )
{
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate( factory, restTemplate );
asyncRestTemplate.setInterceptors( Collections.singletonList( new Oauth2RequestInterceptor( restTemplate ) ) );
return asyncRestTemplate;
}

RestTemplate and Cookie

I need to send an HTTP cookie, I'm using RestTemplate:
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", "SERVERID=c52");
HttpEntity requestEntity = new HttpEntity(null, requestHeaders);
ResponseEntity responses = restTemplate.exchange(webService.getValidateUserUrl(),
HttpMethod.POST, requestEntity, String.class, mapValidateUser);
However, the receiving server doesn't see the cookie.
The default rest template does not use a persistent connetion, here is what I use.
public class StatefullRestTemplate extends RestTemplate
{
private final HttpClient httpClient;
private final CookieStore cookieStore;
private final HttpContext httpContext;
private final StatefullHttpComponentsClientHttpRequestFactory statefullHttpComponentsClientHttpRequestFactory;
public StatefullRestTemplate()
{
super();
HttpParams params = new BasicHttpParams();
HttpClientParams.setRedirecting(params, false);
httpClient = new DefaultHttpClient(params);
cookieStore = new BasicCookieStore();
httpContext = new BasicHttpContext();
httpContext.setAttribute(ClientContext.COOKIE_STORE, getCookieStore());
statefullHttpComponentsClientHttpRequestFactory = new StatefullHttpComponentsClientHttpRequestFactory(httpClient, httpContext);
super.setRequestFactory(statefullHttpComponentsClientHttpRequestFactory);
}
public HttpClient getHttpClient()
{
return httpClient;
}
public CookieStore getCookieStore()
{
return cookieStore;
}
public HttpContext getHttpContext()
{
return httpContext;
}
public StatefullHttpComponentsClientHttpRequestFactory getStatefulHttpClientRequestFactory()
{
return statefullHttpComponentsClientHttpRequestFactory;
}
}
public class StatefullHttpComponentsClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory
{
private final HttpContext httpContext;
public StatefullHttpComponentsClientHttpRequestFactory(HttpClient httpClient, HttpContext httpContext)
{
super(httpClient);
this.httpContext = httpContext;
}
#Override
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri)
{
return this.httpContext;
}
}
You can also extend the RestTemplate:
public class CookieRestTemplate extends RestTemplate {
#Override
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = super.createRequest(url, method);
request.getHeaders().add("Cookie", "SERVERID=c52");
return request;
}
}

Resources