Add Basic Authorization HTTP Headers to SOAP Request with Spring-WS - spring

I have been trying to consume a SOAP service using a Spring application, but I've been absolutely stuck for a while. I expect the solution will be very simple and I'm hoping to be pointed in the right direction.
I am able to successfully make requests via SoapUI. I simply loaded the .WSDL file, selected the method to use, and added the username and password with basic authorization from the 'Auth' menu in SoapUI.
In the Spring application, I've gotten to the point of getting a 401 error, so I believe it's almost there. I referenced these two nice examples below to first add the username and password, and secondly to log the HTTP headers to verify that they are populated correctly.
https://codenotfound.com/spring-ws-log-client-server-http-headers.html
Setting a custom HTTP header dynamically with Spring-WS client
However, neither setting the 'usernamePasswordCredentials', nor setting the connection's request header seems to have any effect. I've also confirmed that the XML body is correct by testing the logged output in SoapUI. So I believe it's just an authorization issue.
Bean/Configuration Class:
#Bean
public Jaxb2Marshaller marshaller() {
System.out.println("BEAN CREATED: MARSHALLER...");
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("..."); // omitted for example
return marshaller;
}
#Bean
public UsernamePasswordCredentials usernamePasswordCredentials() {
return new UsernamePasswordCredentials("testu", "test");
}
#Bean
#DependsOn({"usernamePasswordCredentials"})
public HttpComponentsMessageSender httpComponentsMessageSender(UsernamePasswordCredentials usernamePasswordCredentials) {
HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender();
httpComponentsMessageSender.setCredentials(usernamePasswordCredentials);
return httpComponentsMessageSender;
}
#Bean
#DependsOn({"marshaller"})
public TicketDetailsClient ticketDetailsClient(Jaxb2Marshaller marshaller) {
System.out.println("BEAN CREATED: TICKETDETAILSCLIENT...");
TicketDetailsClient ticketDetailsClient = new TicketDetailsClient();
ticketDetailsClient.setDefaultUri("..."); // omitted for example
ticketDetailsClient.setMarshaller(marshaller);
ticketDetailsClient.setUnmarshaller(marshaller);
return ticketDetailsClient;
}
Bean Method:
public GetTicketDetailsResponse getTicketDetails(long definitionId, long itemId) {
ObjectFactory of = new ObjectFactory();
this.template.setInterceptors(new ClientInterceptor[] {new LogHttpHeaderClientInterceptor()});
GetItemDetailsRequest itemDetailsRequest = new GetItemDetailsRequest();
itemDetailsRequest.setItemDefinitionId(definitionId);
itemDetailsRequest.setItemId(itemId);
GetTicketDetails getTicketDetails = new GetTicketDetails();
getTicketDetails.setGetItemDetailsRequest(itemDetailsRequest);
JAXBElement<GetTicketDetails> elGetTicketDetails = of.createGetTicketDetails(getTicketDetails);
System.out.println("ABOUT TO MARSHALSENDANDRECEIVE...");
GetTicketDetailsResponse ticketDetailsResponse = (GetTicketDetailsResponse) this.template.marshalSendAndReceive(elGetTicketDetails);
return ticketDetailsResponse;
}
Interceptor:
#Override
public boolean handleRequest(MessageContext arg0) throws WebServiceClientException {
// TODO Auto-generated method stub
TransportContext context = TransportContextHolder.getTransportContext();
HttpComponentsConnection connection =(HttpComponentsConnection) context.getConnection();
try {
connection.addRequestHeader("username", "testu");
connection.addRequestHeader("password", "test");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
HttpLoggingUtils.logMessage("Client Request Message", arg0.getRequest());
return true;
}
Result Snippet (This is where I expected to see the username/password headers. Since they are missing, I'm guessing this is the issue):
ABOUT TO MARSHALSENDANDRECEIVE...
2021-08-09 13:46:18.891 INFO 23112 --- [ main] com.fp.fpcustomization.HttpLoggingUtils :
----------------------------
Client Request Message
----------------------------
Accept: text/xml, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
SOAPAction: ""
Content-Type: text/xml; charset=utf-8
Content-Length: 378
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns3:getTicketDetails xmlns:ns3="http://externalapi.business.footprints.numarasoftware.com/"><getItemDetailsRequest><_itemDefinitionId>76894</_itemDefinitionId><_itemId>30201</_itemId></getItemDetailsRequest></ns3:getTicketDetails></SOAP-ENV:Body></SOAP-ENV:Envelope>
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.151 s <<< FAILURE! - in com.fp.fpcustomization.FpcustomizationApplicationTests
[ERROR] RequestTest Time elapsed: 0.51 s <<< ERROR!
org.springframework.ws.client.WebServiceTransportException: [401]
at com.fp.fpcustomization.FpcustomizationApplicationTests.RequestTest(FpcustomizationApplicationTests.java:29)

Following up with the solution that worked for me. This time around, I looked at going about this via the callback function. This led me to the question asked here:
Add SoapHeader to org.springframework.ws.WebServiceMessage
The second answer by Pranav Kumar is what worked for me. So I simply added the callback function to the 'marshalSendAndReceive' function call in the 'GetTicketDetailsResponse getTicketDetails(long definitionId, long itemId)' method. This way, I was able to add the Authorization header that got me past the 401 error that I was getting before.
Object theResponseObject = this.template.marshalSendAndReceive((elGetTicketDetails), new WebServiceMessageCallback(){
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
MimeHeaders mimeHeader = soapMessage.getSaajMessage().getMimeHeaders();
mimeHeader.setHeader("Authorization", "Basic ...==");
}
});
Following this addition, the Authorization header was showing up in the request and the response was successfully returned.

Related

Spring Integration HttpRequestExecutingMessageHandler ContentType Issue

I am facing a problem with Spring Integration. I am trying to execute a rest call via HttpRequestExecutingMessageHandler. My rest endpoint is accepting content-type 'application/json' only.
The problem is that the HttpRequestExecutingMessageHandler is posting with content-type 'text/plain;charset=UTF-8'.
#ServiceActivator(inputChannel = "transformRequestToJsonChannel",
outputChannel = "httpRequestOutChannel")
public Message<?> transformRequest(Message<DocumentConverterRequest>
message)
{
LOG.info("transforming document converter request to json: '{}'",
ObjectToJsonTransformer transformer = new ObjectToJsonTransformer();
transformer.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Object payload = transformer.transform(message).getPayload();
LOG.info("payload: '{}'", payload.toString());
return MessageBuilder.withPayload(payload).build();
}
#Bean
#ServiceActivator(inputChannel = "httpRequestOutChannel")
public HttpRequestExecutingMessageHandler outbound() {
HttpRequestExecutingMessageHandler handler = new
HttpRequestExecutingMessageHandler(documentConverterRestUrl);
handler.setHttpMethod(HttpMethod.POST);
handler.setErrorHandler(httpResponseErrorHandler);
handler.setExpectedResponseType(String.class);
handler.setCharset(Charset.defaultCharset().name());
HeaderMapper<HttpHeaders> mapper = new DefaultHttpHeaderMapper();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE);
mapper.toHeaders(httpHeaders);
handler.setHeaderMapper(mapper);
handler.setOutputChannel(httpResponseChannel());
return handler;
}
How can i override the content-type?
This piece of code does nothing:
HeaderMapper<HttpHeaders> mapper = new DefaultHttpHeaderMapper();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE);
mapper.toHeaders(httpHeaders);
That toHeaders() is called from the HttpRequestExecutingMessageHandler when we receive response. It really useless to use it explicitly in your code, especially in the bean definition phase and when you ignore a result.
You don't need to use an explicit HeaderMapper at all: a default one should be enough for you.
The ObjectToJsonTransformer really maps that setContentType() into a headers of the message it replies:
if (headers.containsKey(MessageHeaders.CONTENT_TYPE)) {
// override, unless empty
if (this.contentTypeExplicitlySet && StringUtils.hasLength(this.contentType)) {
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
}
}
else if (StringUtils.hasLength(this.contentType)) {
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
}
So, there is a proper content type to map. By default HttpRequestExecutingMessageHandler uses:
/**
* Factory method for creating a basic outbound mapper instance.
* This will map all standard HTTP request headers when sending an HTTP request,
* and it will map all standard HTTP response headers when receiving an HTTP response.
* #return The default outbound mapper.
*/
public static DefaultHttpHeaderMapper outboundMapper() {
With an appropriate set of headers to map to HTTP request and from HTTP response.
The new DefaultHttpHeaderMapper() brings just an empty set of headers to map.
Please, raise an issue to improve JavaDocs and Reference Manual to note that default ctor of that class doesn't bring any headers to map.
#Bean
#ServiceActivator(inputChannel = "httpRequestOutChannel")
public HttpRequestExecutingMessageHandler outbound() {
HttpRequestExecutingMessageHandler handler = Http.outboundGateway(documentConverterRestUrl)
.httpMethod(HttpMethod.POST)
.messageConverters(new MappingJackson2HttpMessageConverter())
.mappedRequestHeaders("Content-Type")
.get();
handler.setOutputChannel(httpResponseChannel());
return handler;
}
I removed my ObjectToJsonTransformer, because the messageConverters(new MappingJackson2HttpMessageConverter()) is doing the stuff.
Also i had to add the content-type to my message header: .setHeaderIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)

In case of 401 unauthorized remove header from RequestTemplate and retry

I am inserting an authorization header in a feign request but upon a 401 from the server I am retrying with the same request and same header, resulting in the same error. If I expire manually the token then I end up with 2 Authorization headers old and new resulting in a 400 error. So far I don't see any way of removing the old header and as far as I got was something like this:
#Bean
public RequestInterceptor oauth2ApplicationRequestInterceptor() {
return new OAuth2FeignRequestInterceptor(getOAuth2ClientContext(), oauth2ApplicationResourceDetails()) {
#Override
public void apply(RequestTemplate template) {
if (template.headers().containsKey("Authorization")) {
// if Authorization exists then remove it
} else {
super.apply(template);
}
}
};
Expiring manually the token is the only way for me at the moment if server gives me a 401 error.
I got the same issue.
Here is the way I've solved it
#Override
public void apply(RequestTemplate template) {
// We make a copy of the original headers
Map<String, Collection<String>> originalHeaders = template.headers();
// We copy the original headers in a new map
Map<String, Collection<String>> newHeaders = new HashMap<String, Collection<String>>();
for (Map.Entry<String, Collection<String>> originalEntry : originalHeaders.entrySet()) {
// Except "Authorization" header
if (!"Authorization".equals(originalEntry.getKey())) {
newHeaders.put(originalEntry.getKey(), originalEntry.getValue());
}
}
// This call will clear the template headers Map (see Feign sources)
template.headers(null);
// We add the new "Authorization" header to the new headers
newHeaders.put("Authorization",
Collections.singletonList(String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, getToken())));
// Add the headers to the template
template.headers(newHeaders);
}

Spring RestTemplate + Basic Authentication + Post with request Body: 500 Internal Server Error

I am looking for a working approach for Rest Client using Spring (5.x) RestTemplate with Basic Authentication + passing Request Body as HTTP Post.
NOTE: the service works fine If I hit request using postman/ other rest client, instead of a java client/ test class.
I am getting 500 Internal Server Error
org.springframework.web.client.HttpClientErrorException: 500 Internal Server Error
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:79)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:777)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:730)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:704)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:459)
at com.xxx.xxx.xxx.utils.Util.updateFlag(Util.java:125)
at com.xxx.xxx.xxx.utils.UtilsImplTest.testUpdateFlag(UtilsImplTest.java:122)
My Test class:
#Test
public void testUpdateFlag() {
Request request = new Request();
request.setUserId("aa");
request.setFlag("Y");
request.setValue("D");
Response response = null;
try {
response = util.updateFlag(request);
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
assertNotNull(response);
}
My Implementation util class: where I am setting Basic Authorization in header.
#Autowired private RestTemplate restTemplate;
private HttpHeaders getHeaders(){
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + <base64_encrypted_password>);//500
// headers.setContentType(MediaType.APPLICATION_JSON); //401
return headers;
}
public Response updateFlag(Request request) throws JsonProcessingException, URISyntaxException {
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
HttpEntity<Request> requestEntity = new HttpEntity<>(request, getHeaders());
Response response = restTemplate.postForObject(url, requestEntity, Response.class);
return response;
}
If I comment out the basic authorization line in getHeaders() method, then it throws 401 Unauthorized, which is fairly logical.
org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:79)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:777)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:730)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:704)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:459)
at com.xxx.xxx.xxx.utils.Util.updateFlag(Util.java:125)
at com.xxx.xxx.xxx.utils.UtilsImplTest.testUpdateFlag(UtilsImplTest.java:122)
I have tried almost every option suggested over stackoverflow in similar content, unable to identify the exact root cause why setting authorization in header doesn't validate & throws 500 Internal Server Error.
I have spent quite a handful time investigating, with no luck. Appreciate any pointers.

Why this externa web service call go into error only when the call is performed using Spring RestTemplate?

I am working on a Spring project implementing a simple console application that have to call an external REST web service passing to it a parameter and obtaining a response from it.
The call to this webservice is:
http://5.249.148.180:8280/GLIS_Registration/6
where 6 is the specified ID. If you open this address in the browser (or by cURL tool) you will obtain the expected error message:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<sampleid>IRGC 100000</sampleid>
<genus>Oryza</genus>
<error>PGRFA sampleid [IRGC 100000], genus [Oryza] already registered for this owner</error>
</response>
This error message is the expected response for this request and I correctly obtain it also using cURL tool to perform the request.
So I have to perform this GET request from my Spring application.
To do it I create this getResponse() method into a RestClient class:
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RestClient {
RestTemplate restTemplate;
String uriResourceRegistrationApi;
public RestClient() {
super();
restTemplate = new RestTemplate();
uriResourceRegistrationApi = "http://5.249.148.180:8280/GLIS_Registration/7";
}
public ResponseEntity<String> getResponse() {
ResponseEntity<String> response = restTemplate.getForEntity(uriResourceRegistrationApi, String.class);
return response;
}
}
Then I call this method from this test method:
#Test
public void singleResourceRestTest() {
System.out.println("singleResourceRestTest() START");
ResponseEntity<String> result = restClient.getResponse();
System.out.println("singleResourceRestTest() END");
}
But I am experiencing a very strange behavior, what it happens is:
1)The call to my external web service seems that happens (I saw it from the web services log).
2) The web service retrieve the parameter having value 7 but then it seems that can't use it as done without problem performing the request from the browser or by the shell statment:
curl -v http://5.249.148.180:8280/GLIS_Registration/7
But now, calling in this way, my webservice (I can't post the code because it is a WSO2 ESB flow) give me this error message:
<200 OK,<?xml version="1.0" encoding="UTF-8"?>
<response>
<error>Location information not correct</error>
<error>At least one between <genus> and <cropname> is required</error>
<error>Sample ID is required</error>
<error>Date is required</error>
<error>Creation method is required</error>
</response>,{Vary=[Accept-Encoding], Content-Type=[text/html; charset=UTF-8], Date=[Fri, 05 May 2017 14:07:09 GMT], Transfer-Encoding=[chunked], Connection=[keep-alive]}>
Looking the web service log it seems that performing the call using RestTemplate it have some problem to use the retrieved ID=7 to perform a database query.
I know it looks terribly strange and you can see: "The problem is of your web service and not of the Spring RestTemplate". This is only partially true because I implemented this custom method that perform a low level Http GET call, this callWsOldStyle() (putted into the previous RestClient class):
public void callWsOldStyle() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL restAPIUrl = new URL("http://5.249.148.180:8280/GLIS_Registration/7");
connection = (HttpURLConnection) restAPIUrl.openConnection();
connection.setRequestMethod("GET");
// Read the response
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder jsonData = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
jsonData.append(line);
}
System.out.println(jsonData.toString());
}catch(Exception e) {
e.printStackTrace();
}
finally {
// Clean up
IOUtils.closeQuietly(reader);
if(connection != null)
connection.disconnect();
}
}
Using this method instead the RestTemplate one it works fine and this line:
System.out.println(jsonData.toString());
print the expected result:
<?xml version="1.0" encoding="UTF-8"?><response><sampleid>IRGC 100005</sampleid><genus>Oryza</genus><error>PGRFA sampleid [IRGC 100005], genus [Oryza] already registered for this owner</error></response>
To summarize:
Calling my WS from the browser it works.
Calling my WS using cURL it works.
Calling my WS using my callWsOldStyle() method it works.
Calling my WS using the method that use RestTemplate it go into error when my WS receive and try to handle the request.
So, what can be the cause of this issue? What am I missing? Maybe can depend by some wrong header or something like this?
As Pete said you are receiving an internal server error (status code 500) so you should check the server side of this rest service.
In any case you can do the following for the resttemplate
create an org.springframework.web.client.RequestCallback object if
you need to do something in the request
create an org.springframework.web.client.ResponseExtractor<String>
object in order to extract your data
use the resttemplate
org.springframework.web.client.RequestCallback
public class SampleRequestCallBack implements RequestCallback
{
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException
{
}
}
org.springframework.web.client.ResponseExtractor
public class CustomResponseExtractor implements ResponseExtractor<String>
{
private static final Logger logger = LoggerFactory.getLogger(CustomResponseExtractor.class.getName());
#Override
public String extractData(ClientHttpResponse response) throws IOException
{
try
{
String result = org.apache.commons.io.IOUtils.toString(response.getBody(), Charset.forName("UTF8"));
if( logger.isInfoEnabled() )
{
logger.info("Response received.\nStatus code: {}\n Result: {}",response.getStatusCode().value(), result);
}
return result;
}
catch (Exception e)
{
throw new IOException(e);
}
}
}
REST TEMPLATE CALL
#Test
public void testStack()
{
try
{
String url = "http://5.249.148.180:8280/GLIS_Registration/6";
String response = restTemplate.execute(url, HttpMethod.GET, new SampleRequestCallBack(), new CustomResponseExtractor());;
logger.info(response);
}
catch (Exception e)
{
logger.error("Errore", e);
}
}
Angelo

Why OAuth2AccessTokenSupport always send POST request ??

I'm working with a Spring Boot + Spring Security OAuth2 to consume the Restful Oauth2 service.
Our Oauth2 service is always expects HTTP GET But OAuth2AccessTokenSupport always sending HTTP POST.
Result:
resulted in 405 (Method Not Allowed); invoking error handler
protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource,
MultiValueMap<String, String> form, HttpHeaders headers) throws OAuth2AccessDeniedException {
try {
this.authenticationHandler.authenticateTokenRequest(resource, form, headers);
this.tokenRequestEnhancer.enhance(request, resource, form, headers);
AccessTokenRequest copy = request;
ResponseExtractor delegate = getResponseExtractor();
ResponseExtractor extractor = new ResponseExtractor(copy, delegate) {
public OAuth2AccessToken extractData(ClientHttpResponse response) throws IOException {
if (response.getHeaders().containsKey("Set-Cookie")) {
this.val$copy.setCookie(response.getHeaders().getFirst("Set-Cookie"));
}
return ((OAuth2AccessToken) this.val$delegate.extractData(response));
}
};
return ((OAuth2AccessToken) getRestTemplate().execute(getAccessTokenUri(resource, form), getHttpMethod(),
getRequestCallback(resource, form, headers), extractor, form.toSingleValueMap()));
} catch (OAuth2Exception oe) {
throw new OAuth2AccessDeniedException("Access token denied.", resource, oe);
} catch (RestClientException rce) {
throw new OAuth2AccessDeniedException("Error requesting access token.", resource, rce);
}
}
<b>protected HttpMethod getHttpMethod() {
return HttpMethod.POST;
}</b>
protected String getAccessTokenUri(OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form) {
String accessTokenUri = resource.getAccessTokenUri();
if (this.logger.isDebugEnabled()) {
this.logger.debug(new StringBuilder().append("Retrieving token from ").append(accessTokenUri).toString());
}
StringBuilder builder = new StringBuilder(accessTokenUri);
String separator;
if (getHttpMethod() == HttpMethod.GET) {
separator = "?";
if (accessTokenUri.contains("?")) {
separator = "&";
}
for (String key : form.keySet()) {
builder.append(separator);
builder.append(new StringBuilder().append(key).append("={").append(key).append("}").toString());
separator = "&";
}
}
return builder.toString();
}
Can Anyone explain me why OAuth2AccessTokenSupport always returns POST and
How to send HTTP GET request
To enable GET requests for the token endpoint, you need to add the following in your AuthorizationServerConfigurerAdapter:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
As for why only POST by default: I think that is due to GET requests potentially sending username and password information as request params (this is certainly the case for password grant). These may well be visible in web server logs, while POST body data is not.
Indeed the RFC for OAuth2 declares that the client must use HTTP POST when requesting an access token (https://www.rfc-editor.org/rfc/rfc6749#section-3.2)

Resources