I have implemented Spring XwsSecurityInterceptor and receiving soap message with <wsse:UsernameToken/> inside the <wsse:Security/> tag (OASIS WS-Security). It works fine.
Now I am trying to implement a logging interceptor to log the request/response soap messages in DB.
I can get the Security element in getSource() method of my custom logging interceptor (which extends org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor):
#Override
protected Source getSource(WebServiceMessage message) {
SaajSoapMessage soap = (SaajSoapMessage) message;
logger.info(Utils.getSoapEnvelopeAsString(soap));
// this envelop contains the <wsse:Security/> element as expected
// ...
// ...
}
But My problem is, when I extract the envelop inside my endpoint method, I don't get the <wsse:Security/> element in the header anymore.
public JAXBElement<MyResponseType> getRecepientInfo(#RequestPayload JAXBElement<MyRequestType> request, MessageContext messageContext) {
SaajSoapMessage soapReq = (SaajSoapMessage) messageContext.getRequest();
logger.info(Utils.getSoapEnvelope(soapReq));
// this envelop doesn't contain the <wsse:Security/> element
}
Here is the code for Utils.getSoapEnvelope(soap):
public static String getSoapEnvelope(SaajSoapMessage soapMessage) {
SoapEnvelope envelope = soapMessage.getEnvelope();
String envelopeMessge = "";
try {
envelopeMessge = Utils.getSourceAsString(envelope.getSource());
} catch (Exception e) {
// TODO handle Exception here.
}
return envelopeMessge;
}
public static String getSourceAsString(Source source) throws Exception{
TransformerFactory tfactory = TransformerFactory.newInstance();
Transformer xform = tfactory.newTransformer();
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
xform.transform(source, result);
return writer.toString();
}
Does spring remove the <wsse:Security/> element from the header after authentication has been completed? Or, I am doing something wrong here?
How should I get the <wsse:Security/> element from header inside endpoint method?
I know this is a late answer but for whom it may interest I found out how to solve this.
You need to modify your securityPolicy.xml file so that the security header is kept. Simply set the attribute retainSecurityHeader to true. Here is an example of such a file:
<xwss:SecurityConfiguration retainSecurityHeader="true" dumpMessages="false" xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:UsernameToken digestPassword="false" useNonce="false" id="someId"/>
</xwss:SecurityConfiguration>
Related
I am calling a service in an orders controller which receives a multipart file and processes it and saving it into a database. I am trying to create a Spring Rest Doc for it but it is not even hitting the endpoint. I am creating a list of orders which is what the service expects. It receives the order as a stream as shown and converts into a stream of orders before saving it into a database. I have shown the main part of the controller and my code for generating the rest docs. When I run the code I get the following exception, it never even hits the endpoint when I set a breakpoint. I also used fileupload() but that did not work either.
Exception is:
Content type = application/json
Body = {"path":"/orders/order_reception","exceptionName":
"MissingServletRequestPartException","message":"Required request part 'uploadFile' is not
present",
"rootExceptionName":"MissingServletRequestPartException",
"rootMessage":"MissingServletRequestPartException: Required request part 'uploadFile' is not present"}
#RestController
#RequestMapping(value = "/orders")
#Validated
class OrderController{
#PostMapping(path = "/order_reception")
public ResponseEntity receiveData(#RequestPart MultipartFile uploadFile,
HttpServletRequest request,
HttpServletResponse response) {
if (!uploadFile.isEmpty()) {
try {
Reader reader = new InputStreamReader(request.getInputStream()));
... save file
return new ResponseEntity<>(HttpStatus.HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
#Test
public void sendData() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Order order = repository.getOrder("1233333");
List<Order> orderList = new ArrayList<>():
resourceList.add(order);
MockMultipartFile orderFile = new MockMultipartFile("order-data", "order.json", "application/json",
mapper.writeValueAsString(orderList).getBytes(Charset.defaultCharset()));
mockMvc.perform(multipart("/orders/order_reception")
.file(orderFile))
.andExpect(status().isCreated())
.andDo(document("send-order",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())));
}
Thank you Marten Deinum, your suggestion that the file name was wrong fixed it.
I simply changed name in the MockMultipartFile( "uploadsFile", ...)
Using restTemplate.exchange(uri, method, entity, responseType) to make a REST call fails with a RestClientException when the response is of the wrong responseType. E.g.,
org.springframework.web.client.RestClientException: Error while extracting response for type [java.util.List<java.lang.Byte>] and content type [application/json;charset=UTF-8];
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Numeric value (281759) out of range of Java byte
Since this is not a RestClientResponseException we don't seem to have access to the response data like status code and body (not even in their raw form).
Is there a way to get (raw) data from the original (unparsable) response? (for logging)
Try parsing the response as String. See this answer - the similar concept can be used with the exchange method.
EDIT: If the exception does not occur always and you still want to be able to map the correct responses easily, you could override the corresponding MessageConverter (which is actually throwing the exception) and do anything you want afterwards, because the converter gives you a raw HttpInputMessage.
Assuming you are using MappingJackson2HttpMessageConverter it should look sth. like this (not tested though):
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
return new MappingJackson2HttpMessageConverter(objectMapper) {
#Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try {
return super.read(type, contextClass, inputMessage);
} catch (HttpMessageNotReadableException e) {
// LOG here...
throw e;
}
}
};
}
Try to add StringHttpMessageConverter in RestTemplate's messageConverters
StringHttpMessageConverter stringHttpMessageConverter
= new StringHttpMessageConverter(StandardCharsets.UTF_8);
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter
= new MappingJackson2HttpMessageConverter(objectMapper);
RestTemplate restTemplate = new RestTemplate(factory);
restTemplate.setMessageConverters(
List.of(stringHttpMessageConverter, jackson2HttpMessageConverter));
final RestTemplate restTemplate = new RestTemplate();
try {
restTemplate.exchange(uri, method, entity, responseType);
} catch (RestClientException e) {
//for logging exact message
restTemplate.getForObject("https://httpbin.org/ip", String.class);
}
I'm using RestTemplate to call an external resource using GET where the URI is https://external.com/resource/{resourceID}.
Accordingly, my RestTemplate calls looks like the following:
restTemplate.getForObject("https://external.com/resource/{resourceID}", String.class, uriMapObject)
Where uriMapObject is a map containing the ID variable.
I have also set a ClientHttpRequestInterceptor for my RestTemplate where the interceptor's function is to create a log item for that call and send it to ElasticSearch for logging.
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution execution) throws IOException {
Map<String, Object> reqHeaders = new HashMap<>();
try{
// getPath() already contains the URI variable
String uri = httpRequest.getURI().getPath();
} catch (IllegalStateException e){
log.warn(e.getMessage());
}
.....
return response;
}
The issue is that within the ClientHttpRequestInterceptor method, I'm unable to access the original URI template used to derive the actual URL to call. I can only access the actual URL from the HttpRequest object which already has a unique identifier in it which in turn makes it impossible to aggregate all calls to https://external.com/resource/{resourceID} as a single pattern in Elastic.
Is there some way where I can get the URI template from within the interceptor?
URI template expansion is specified by the RestTemplate's UriTemplateHandler. You can customize the behavior of this by calling RestTemplate#setUriTemplateHandler, specifying your own implementation.
I would suggest creating a delegate object for a UriTemplateHandler, and wrapping what is set by default on the RestTemplate.
class LoggingUriTemplateHandler implements UriTemplateHandler {
private UriTemplateHandler delegate;
LoggingUriTemplateHandler(final UriTemplateHandler delegate) {
this.delegate = delegate;
}
public URI expand(final String template, final Map<String, ?> vars) {
//log template here
return this.delegate.expand(template, vars);
}
public URI expand(final String template, final Object... vars) {
//log template here
return this.delegate.expand(template, vars);
}
}
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
While implementing a global exception handler in Spring, I noticed that in case of a not recognized Accept header, Spring would throw it's own internal error. What I need is to return a custom JSON error structure instead. Works fine for application specific exceptions and totally fails for Spring HttpMediaTypeNotAcceptableException.
This code tells me "Failed to invoke #ExceptionHandler method: public java.util.Map RestExceptionHandler.springMalformedAcceptHeaderException()" when I try to request a page with incorrect Accept header. Any other way to return custom JSON for spring internal exceptions?
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
#ResponseBody
public Map<String, String> springMalformedAcceptHeaderException() {
Map<String, String> test = new HashMap<String, String>();
test.put("test", "test");
return test;
}
}
Eventually figured that the only way is to do the json mapping manually.
#ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
#ResponseBody
public String springMalformedAcceptHeaderException(HttpServletResponse response) {
// populate errorObj, set response headers, etc
ObjectWriter jsonWriter = new ObjectMapper().writer();
try {
return jsonWriter.writeValueAsString(errorObj);
} catch(Exception e){}
return "Whatever";
}