Proper way to convert Spring HATEOAS Link to Object - spring

I have a very simple controller the makes an HTTP request and receives some resources in HATEOAS format.
package com.provider.spring.controller;
import java.util.List;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.provider.employee.EmployeeDTO;
import com.provider.http.HttpComponentsClientHttpRequestFactoryBasicAuth;
import com.provider.spring.rest.Resource;
#Controller
public class EmployeeController {
private static final String REL_SELF = "self";
private static final String REL_SEARCH = "search";
private static final String REL_EMPLOYEE = "employee";
private static final String RESOURCE_URI = "http://localhost:8080/employees";
private RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactoryBasicAuth("user", "password"));
private List<EmployeeDTO> employees;
#RequestMapping("/employees")
public String getAllEmployees() {
String result = null;
try {
String resultBody = restTemplate.getForObject(RESOURCE_URI, String.class);
ObjectMapper objectMapper = new ObjectMapper();
Resource<EmployeeDTO> resource = objectMapper.readValue(resultBody, Resource.class);
// Get objects with relation "employee"
for(Link l : resource.getLinks()) {
if(l.getRel().equals(REL_EMPLOYEE)) {
// TODO: Construct EmployeeDTO from Link.
// TODO: Add EmployeeDTO to list.
}
}
}
catch(Exception e) {
e.printStackTrace();
result = "error";
return result;
}
return result;
}
}
Is there an existing prefered or standard way of converting Links to Objects?
See here for details on the EmployeeDTO: https://gist.github.com/Xerosigma/64469a30355f5de0228a

A couple ways to do this with Jackson:
Without custom converter:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
RestTemplate restTemplate= new RestTemplate();
String resultBody = restTemplate.getForObject(link.getHref(), String.class);
EmployeeDTO resource = objectMapper.readValue(resultBody, EmployeeDTO.class);
With custom converter:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
RestTemplate restTemplate= new RestTemplate();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
restTemplate.setMessageConverters(Collections.<HttpMessageConverter<?>> singletonList(converter));
EmployeeDTO resource = restTemplate.getForObject(link.getHref(), EmployeeDTO.class);

You can register some MessageConverters in the constructor of the RestTemplate or use the setMessageConverters. Both take a List of HttpMessageConverters as a parameter.
If you have it configured like that, you can use the exchange or the getForObject-method with your expected resource-type. The RestTemplate will run through all the converters and check if it can convert the json/xml/... to your object.
Here is an example:
List<HttpMessageConverter<?>> converters = new LinkedList<>();
converters.add(new MappingJacksonHttpMessageConverter());
RestTemplate template = new RestTemplate(converters);
// or use the set method
template.setMessageConverters(converters);
// make url call
Resource<EmployeeDTO> resource = template.getForObject("url", Resource.class);

Related

OAuth2, Access /oauth2/token ressource behind a proxy

I need to consume an API securised by OAuth2 with WebClient. I have configure the OAuth2AuthorizedClientManager to manage the access token and refresh it when it need to be.
However I encounter some issue, java.net.UnknownHostException. There is a proxy between my Application and the OAuth2 token ressource and I do not know how to configure it.
What I have try :
Test it in an other environment without proxy and it's work. My OAuth2AuthorizedClientManager configuration is correct.
System.setProperty(), not a solution, I have several proxy to manage.
maybe I am misunderstanding some OAuth2 notions
Here some code:
application.properties
spring.security.oauth2.client.registration.client.client-id=clientId
spring.security.oauth2.client.registration.client.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.client.client-secret=clientSecret
spring.security.oauth2.client.provider.client.token-uri=URI/oauth2/token
WebClientConfig
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService clientService)
{
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, clientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
#Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("client");
return WebClient.builder()
.baseUrl("URI")
.clientConnector(getReactorClientHttpConnector(url))
.apply(oauth2Client.oauth2Configuration())
.build();
}
My test
#Autowired
WebClient webClient;
public void test() {
RequestHeadersSpec<?> request = webClient.get()
.uri("/heartbeats");
}
Error
org.springframework.security.oauth2.core.OAuth2AuthorizationException: [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: I/O error on POST request for "URI/oauth2/token": URI; nested exception is java.net.UnknownHostException: URI
My question is, How to configure a proxy for the OAuth2AuthorizedClientManager ?
Please feel free to ask for clarification.
Any help would be appreciated. Thanks
We had some similar problem in the past and solved it by following configuration.
#Configuration
public class AuthConfiguration {
#Bean
public JwtDecoderFactory<ClientRegistration> jwtDecoderFactory() {
return new CustomOidcIdTokenDecoderFactory(jwksRestTemplate());
}
#Bean
public DefaultAuthorizationCodeTokenResponseClient oAuth2AccessTokenResponseClient() {
var defaultAuthorizationCodeTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
defaultAuthorizationCodeTokenResponseClient.setRestOperations(tokenRestTemplate());
return defaultAuthorizationCodeTokenResponseClient;
}
#Bean
public RestTemplate jwksRestTemplate() {
return new RestTemplate(requestFactory());
}
#Bean
public RestTemplate tokenRestTemplate() {
// Copied from constructor of DefaultAuthorizationCodeTokenResponseClient
var restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
restTemplate.setRequestFactory(requestFactory());
return restTemplate;
}
private ClientHttpRequestFactory requestFactory() {
var requestFactory = new SimpleClientHttpRequestFactory();
var proxy = new Proxy(Type.HTTP, new InetSocketAddress("my.host.com", 8080));
requestFactory.setProxy(proxy);
return requestFactory;
}
}
Maybe this helps. Also the following class needs to be added because it is package private in Spring ;)
package org.springframework.security.oauth2.client.oidc.authentication;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.client.oidc.authentication.DefaultOidcIdTokenValidatorFactory;
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
import java.util.function.Function;
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri;
/**
* extension for {#link OidcIdTokenDecoderFactory} to mock the JWKS request
*/
public class CustomOidcIdTokenDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER =
new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters());
private Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm.RS256;
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = new DefaultOidcIdTokenValidatorFactory();
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
private final RestTemplate restTemplate;
public CustomOidcIdTokenDecoderFactory(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
NimbusJwtDecoder jwtDecoder = buildDecoder(clientRegistration);
jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(clientRegistration));
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
this.claimTypeConverterFactory.apply(clientRegistration);
if (claimTypeConverter != null) {
jwtDecoder.setClaimSetConverter(claimTypeConverter);
}
return jwtDecoder;
}
private NimbusJwtDecoder buildDecoder(ClientRegistration clientRegistration) {
JwsAlgorithm jwsAlgorithm = this.jwsAlgorithmResolver.apply(clientRegistration);
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
if (!StringUtils.hasText(jwkSetUri)) {
OAuth2Error oauth2Error = new OAuth2Error(
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
"Failed to find a Signature Verifier for Client Registration: '" +
clientRegistration.getRegistrationId() +
"'. Check to ensure you have configured the JwkSet URI.",
null
);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
return withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).restOperations(restTemplate).build();
}
}

Spring boot 1.5.1 - Inside registered Filter can not access Spring context #Value

I could not read the below properties value, where as I can read the same value in to my other controllers But not in the furnished Filter
#Value("${session.timout.mins}")
private String sessionTimeout;
My Filter
#Configuration
public class AuthoriseFilter implements Filter {
Logger logger = Logger.getLogger(AuthoriseFilter.class);
private static String ADMIN_LOGIN_EMIAL = "sdsds#mail.com";
private static String ISSUER_LOGIN_EMIAL = "dsds#mail.com";
#Value("${session.timout.mins}")
private String sessionTimeout;
This is how Filter is registered inside Spring context
package com.digrec.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.digrec.filter.AuthenticateFilter;
import com.digrec.filter.AuthoriseFilter;
#Configuration
public class FilterConfig {
#Bean
public FilterRegistrationBean authoriseFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
String[] patterns = new String[]{"/login", "/logout"};
registrationBean.setName("authFilter");
AuthoriseFilter authFilter = new AuthoriseFilter();
registrationBean.setFilter(authFilter);
registrationBean.addUrlPatterns(patterns);
registrationBean.setOrder(1);
return registrationBean;
}
#Bean
public FilterRegistrationBean authenticateFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
String[] patterns = new String[]{"/api/*", "/adminapi/*"};
registrationBean.setName("authenticateFilter");
AuthenticateFilter authFilter = new AuthenticateFilter();
registrationBean.setFilter(authFilter);
registrationBean.addUrlPatterns(patterns);
registrationBean.setOrder(1);
return registrationBean;
}
}
The problem is in your FilterConfig.class you are creating the filter instances yourself by calling constructor. This should be done using spring managed bean like below. I'm showing only for AuthoriseFilter, similar should be done for AuthenticationFilter also.
#Configuration
public class FilterConfig {
#Bean
public FilterRegistrationBean authoriseFilterRegistrationBean(#Autowired AuthoriseFilter authFilter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
String[] patterns = new String[]{"/", "/list"};
registrationBean.setName("authFilter");
registrationBean.setFilter(authFilter);
registrationBean.addUrlPatterns(patterns);
registrationBean.setOrder(1);
return registrationBean;
}
}
And your filter isn't configuration so it should be annotated with #Component
#Component
public class AuthoriseFilter implements Filter {
Logger logger = Logger.getLogger(AuthoriseFilter.class);
private static String ADMIN_LOGIN_EMIAL = "sdsds#mail.com";
private static String ISSUER_LOGIN_EMIAL = "dsds#mail.com";
#Value("${session.timout.mins}")
private String sessionTimeout;
}

Using #Headers with dynamic values in Feign client + Spring Cloud (Brixton RC2)

Is it possible to set dynamic values to a header ?
#FeignClient(name="Simple-Gateway")
interface GatewayClient {
#Headers("X-Auth-Token: {token}")
#RequestMapping(method = RequestMethod.GET, value = "/gateway/test")
String getSessionId(#Param("token") String token);
}
Registering an implementation of RequestInterceptor adds the header but there is no way of setting the header value dynamically
#Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Auth-Token", "some_token");
}
};
}
I found the following issue on github and one of the commenters (lpborges) was trying to do something similar using headers in #RequestMapping annotation.
https://github.com/spring-cloud/spring-cloud-netflix/issues/288
Kind Regards
The solution is to use #RequestHeader annotation instead of feign specific annotations
#FeignClient(name="Simple-Gateway")
interface GatewayClient {
#RequestMapping(method = RequestMethod.GET, value = "/gateway/test")
String getSessionId(#RequestHeader("X-Auth-Token") String token);
}
The #RequestHeader did not work for me. What did work was:
#Headers("X-Auth-Token: {access_token}")
#RequestLine("GET /orders/{id}")
Order get(#Param("id") String id, #Param("access_token") String accessToken);
#HeaderMap,#Header and #Param didn't worked for me, below is the solution to use #RequestHeader when there are multiple header parameters to pass using FeignClient
#PostMapping("/api/channelUpdate")
EmployeeDTO updateRecord(
#RequestHeader Map<String, String> headerMap,
#RequestBody RequestDTO request);
code to call the proxy is as below:
Map<String, String> headers = new HashMap<>();
headers.put("channelID", "NET");
headers.put("msgUID", "1234567889");
ResponseDTO response = proxy.updateRecord(headers,requestDTO.getTxnRequest());
I have this example, and I use #HeaderParam instead #RequestHeader :
import rx.Single;
import javax.ws.rs.Consumes;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
#Consumes(MediaType.APPLICATION_JSON)
public interface FeignRepository {
#POST
#Path("/Vehicles")
Single<CarAddResponse> add(#HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, VehicleDto vehicleDto);
}
You can use HttpHeaders.
#PostMapping(path = "${path}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<?> callService(#RequestHeader HttpHeaders headers, #RequestBody Object object);
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "1234");
headers.add("CLIENT_IT", "dummy");
return headers;
}
I use #HeaderMap as it seems very handy if you are working with Open feign. Using this way you can pass header keys and values dynamically.
#Headers({"Content-Type: application/json"})
public interface NotificationClient {
#RequestLine("POST")
String notify(URI uri, #HeaderMap Map<String, Object> headers, NotificationBody body);
}
Now create feign REST client to call the service end point, create your header properties map and pass it in method parameter.
NotificationClient notificationClient = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(customDecoder())
.target(Target.EmptyTarget.create(NotificationClient.class));
Map<String, Object> headers = new HashMap<>();
headers.put("x-api-key", "x-api-value");
ResponseEntity<String> response = notificationClient.notify(new URI("https://stackoverflow.com/example"), headers, new NotificationBody());
A bit late to the game here, but if one needs an enforced, templated value, I discovered that this works in Spring Boot. Apparently, as long as the toString() gives a valid header value, you can use any type.
#FeignClient(
name = "my-feign-client",
url = "http://my-url.com"
)
public interface MyClient {
#GetMapping(
path = "/the/endpoint",
produces = MediaType.APPLICATION_JSON_VALUE
)
DataResponse getData(#RequestHeader(HttpHeaders.AUTHORIZATION) BearerHeader bearerHeader);
final class BearerHeader {
private final String token;
private BearerHeader(String token) {
this.token = token;
}
#Override
public String toString() {
return String.format("Bearer %s", token);
}
public static BearerHeader of(String token) {
return new BearerHeader(token);
}
}

How To Use Spring RESTTemplate To Post Data to a Web Service

I have written a jersey client code to call a webservice.And it is working fine. Now insteade of jersey i have to use the Spring rest template to call the webservice . So please help me in converting the jersey code to spring 4.0.
Here is my jersey code.
ServiceClient.java
package com.api.Client;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.tcs.DataShare.dao.ConfigureLogDao;
import com.tcs.ngps.sip.modeler.utils.ProductConfiguration;
public class ServiceClient {
static final Logger LOGGER = LoggerFactory
.getLogger(ServiceClient.class);
private WebResource service;
private ClientResponse response;
private String serviceName;
private String vmAddress;
private String portNumber;
private String WAR_FILE_NAME;
public ServiceClient(String localhost, String port,
String serviceName) {
this.vmAddress = localhost;
this.portNumber = port;
this.serviceName = serviceName;
System.out.println("vm address:" + vmAddress + "port:" + portNumber);
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WAR_FILE_NAME = ProductConfiguration
.getStringValueForProductProperty("DATASHARE_SERVER_WAR_FILE_NAME");
service = client.resource(UriBuilder.fromUri(
"http://" + vmAddress + ":" + portNumber + "/" + WAR_FILE_NAME)
.build());
LOGGER.debug("WAR_FILE_NAME in the client program"+WAR_FILE_NAME);
System.out.println("service is" + service);
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getVmAddress() {
return vmAddress;
}
public void setVmAddress(String vmAddress) {
this.vmAddress = vmAddress;
}
public String getPortNumber() {
return portNumber;
}
public void setPortNumber(String portNumber) {
this.portNumber = portNumber;
}
public InputStream zipFolder(String folderToBeZipped,String transactionId) {
LOGGER.debug("ServiceClient :: zipFolder() : Calling zipFolder Service -> folderToBeZipped: "
+ folderToBeZipped);
String header = getServiceName();
response = service.path("rest").path("DataShareService")
.path("zipFolder")
.type(MediaType.APPLICATION_JSON).header("header", header)
.post(ClientResponse.class, folderToBeZipped);
LOGGER.debug("INSIDE THE ZIP METHOD FOR CHECKING ZIP METHOD");
InputStream inputStream = response.getEntityInputStream();
LOGGER.debug("DataShareServiceClient :: zipFolder() : Calling zipFolderWithSubsequestFolder Service done");
return inputStream;
}
}
You can find it out with a simple search.
According to this tutorial from spring you can do it like this:
// Set the Content-Type header
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(new MediaType("application","json"));
HttpEntity<String> requestEntity = new HttpEntity<String>(folderToBeZipped, requestHeaders);
// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();
// Add the Jackson and String message converters
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
// Make the HTTP POST request, marshaling the request to JSON, and the response to a String
ResponseEntity<InputStream> responseEntity = restTemplate.exchange(your_url, HttpMethod.POST, requestEntity, InputStream.class);
String result = responseEntity.getBody();
hope this helps.

WebServiceTemplate with Basic Auth using HttpComponentsMessageSender

I am trying to test a Spring Web Service which is currently secured with Basic Authentication underneath. For these tests, I have written a Web Service client using Spring's WebServiceTemplate class.
My Web Service client calls to the Web Service work okay when I create the template's MessageSender as a org.springframework.ws.transport.http.CommonsHttpMessageSender object bean with org.apache.commons.httpclient.UsernamePasswordCredentials and, although the client works, the code has a warning highlighted saying that the CommonsHttpMessageSender class is now deprecated and that I should be using HttpComponentsMessageSender instead.
I have tried re-configuring the client's WebServiceTemplate to work using the newer HttpComponentsMessageSender class, but I am unable to have the basic auth part configured correctly with it. For the new HttpComponentsMessageSender class, I have created credentials using the org.apache.http.auth.UsernamePasswordCredentials class but, when I make a call to the Web Service, the credentials seem to not be available with the request? Is there a working example of a WebServiceTemplate client anywhere that uses these newer classes for authenticating requests, etc?
Jars that my working code with old deprecated classes uses: commons-httpclient-3.1, spring-ws-core-2.2.0.RELEASE.
Jars that my NON-working code with newer classes uses: httpclient-4.3.4, httpcore-4.3.2, spring-ws-core-2.2.0.RELEASE.
Test Configuration as it stands for NON-working code:
package com.company.service.a.ws.test.config;
import java.io.IOException;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
import org.springframework.ws.transport.http.HttpComponentsMessageSender;
#PropertySource("classpath:/${environment}-use-case-data.properties")
#ComponentScan(basePackages = "com.company.service.a.ws.test")
#Configuration
public class TestConfig {
#Value("${ws.url}")
private String wsUrl;
#Value("${ws.username}")
private String username;
#Value("${ws.password}")
private String password;
private static final Logger logger = LogManager.getLogger();
#Bean
public SaajSoapMessageFactory messageFactory() {
return new SaajSoapMessageFactory();
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.company.service.a.ws.model.data");
return marshaller;
}
#Bean RequestConfig requestConfig() {
RequestConfig requestConfig = RequestConfig.custom()
.setAuthenticationEnabled(true)
.build();
return requestConfig;
}
#Bean
#DependsOn( value = "propertyConfigurer" )
public UsernamePasswordCredentials credentials() {
logger.debug("creating credentials for username: {} passowrd={}",
username, password);
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
username, password);
return credentials;
}
#Bean
public CredentialsProvider credentialsProvider() {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, credentials());
return credentialsProvider;
}
private static class ContentLengthHeaderRemover implements HttpRequestInterceptor{
#Override
public void process(HttpRequest request, HttpContext context)
throws HttpException, IOException {
// fighting org.apache.http.protocol.RequestContent's
// ProtocolException("Content-Length header already present");
request.removeHeaders(HTTP.CONTENT_LEN);
}
}
#Bean
public HttpComponentsMessageSender messageSender() {
RequestConfig requestConfig = RequestConfig.custom()
.setAuthenticationEnabled(true)
.build();
HttpClientBuilder httpClientBuilder = HttpClients.custom();
HttpClient httpClient = httpClientBuilder
.addInterceptorFirst(new ContentLengthHeaderRemover())
.setDefaultRequestConfig(requestConfig)
.setDefaultCredentialsProvider(credentialsProvider())
.build();
HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender(httpClient);
return messageSender;
}
#Bean( name = "propertyConfigurer" )
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
PropertySourcesPlaceholderConfigurer configurer =
new PropertySourcesPlaceholderConfigurer();
return configurer;
}
#Bean
public WebServiceTemplate webServiceTemplate() {
logger.debug("creating webServiceTemplate to url: {}", wsUrl);
WebServiceTemplate webServiceTemplate = new WebServiceTemplate(messageFactory());
webServiceTemplate.setDefaultUri(wsUrl);
webServiceTemplate.setMarshaller(marshaller());
webServiceTemplate.setUnmarshaller(marshaller());
webServiceTemplate.setMessageSender(messageSender());
return webServiceTemplate;
}
}
Thanks in advance,
PM
Use HttpComponentsMessageSender with UsernamePasswordCredentials. Note that HttpComponentsMessageSender must be created as Spring bean or you must call afterPropertiesSet manually to be http client correctlly set up.
This works for me:
#Configuration
public class WsClientConfiguration {
#Bean
public ESignatureProcessorClient eSignatureProcessorClient() {
ESignatureProcessorClient client = new ESignatureProcessorClient();
client.setWebServiceTemplate(mwWebServiceTemplate());
return client;
}
#Bean
public WebServiceTemplate mwWebServiceTemplate() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("cz.csas.services.esignatureprocessor.v02_02");
WebServiceTemplate template = new WebServiceTemplate(marshaller, marshaller);
template.setDefaultUri("https://osb-st2.vs.csin.cz:5001/CSMW/WS_MW_ESignatureProcessor_v02_02");
template.setMessageSender(defaultMwMessageSender());
return template;
}
#Bean
public HttpComponentsMessageSender defaultMwMessageSender() {
HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender();
messageSender.setCredentials(new UsernamePasswordCredentials("user", "password"));
return messageSender;
}
}
This is workout for our project using org.apache.httpcomponents :
httpclient-4.5.3, httpcore-4.4.6
We create interceptor header RequestDefaultHeaders reqHeader = new RequestDefaultHeaders(headers) and then add to httpClient using .addInterceptorLast(reqHeader) when building CloseableHttpClient
Configuration class :
import org.apache.http.message.BasicHeader;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.Header;
import org.apache.http.client.protocol.RequestDefaultHeaders;
#Bean
HttpClient createHttpClient() {
List<Header> headers = new ArrayList<>();
BasicHeader authHeader = new BasicHeader("Authorization", "Basic " + base64authUserPassword());
headers.add(authHeader);
// add more header as more as needed
RequestDefaultHeaders reqHeader = new RequestDefaultHeaders(headers);
CloseableHttpClient httpClient =
HttpClients.custom()
.addInterceptorFirst(new HttpComponentsMessageSender.RemoveSoapHeadersInterceptor())
.addInterceptorLast(reqHeader)
.build();
return httpClient;
}
#Bean
public HttpComponentsMessageSender defaultMyMessageSender()
throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender(createHttpClient());
//messageSender.setCredentials(credentials());
return messageSender;
}
#Bean
WebServiceTemplate webServiceTemplate() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException{
WebServiceTemplate wsTemplate = new WebServiceTemplate();
wsTemplate.setDefaultUri(endpointURI);
wsTemplate.setMessageSender(defaultMyMessageSender());
return wsTemplate;
}
One solution I have used is to create a custom WebServiceMessageSender with a custom CredentialsProvider. This solution also sets a route planner that respects the default java proxy settings.
#Configuration
public class WebServiceConfiguration {
#Bean
public WebServiceMessageSender webServiceMessageSender(#Value("${endpoint.uri}") endpointUri,
#Value("${endpoint.username}") String username,
#Value("${endpoint.password}") String password) throws Exception {
SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(
ProxySelector.getDefault());
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(new AuthScope(endpointUri.getHost(), endpointUri.getPort(), ANY_REALM, ANY_SCHEME), new UsernamePasswordCredentials(username, password););
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.addInterceptorFirst(new HttpComponentsMessageSender.RemoveSoapHeadersInterceptor())
.setDefaultCredentialsProvider(credentialsProvider)
.build();
return new HttpComponentsMessageSender(httpclient);
}
}
In the end, to make Basic Authentication work with the Spring WebServiceTemplate in spring-ws-xxx.2.2.0.RELEASE using current httpclient-4.3.+, httpcore-4.3.+ classes, I've added a preemptive authentication interceptor to the HttpClient (as suggested by #Oliv in Preemptive Basic authentication with Apache HttpClient 4). Note that, as pointed out by #Oliv, this solution adds authentication to ALL requests made.
I am still not sure if this is the best way to configure the Spring WebServiceTemplate but it is the only way I have found (so far) of enabling preemptive authentication without direct access to the HttpClient's HttpClientContext object. Any simpler better answers I would very much welcome...
Interceptor code:
private static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
public void process(final HttpRequest request, final HttpContext context)
throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(
HttpClientContext.TARGET_AUTH_STATE);
// If no auth scheme is avaialble yet, initialize it preemptively
if ( authState.getAuthScheme() == null ) {
CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
HttpClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(
HttpCoreContext.HTTP_TARGET_HOST);
Credentials creds = credsProvider.getCredentials(
new AuthScope(targetHost.getHostName(), targetHost.getPort()));
if ( creds == null ) {
throw new HttpException("no credentials available for preemptive "
+ "authentication");
}
authState.update(new BasicScheme(), creds);
}
}
}
Thread is old but to summaries.
As per spring documentation:
UsernamePasswordCredentials and HttpComponentsMessageSender should be spring beans. So define beans and inject them. It should solve the problem.

Resources