How to read payload in Jersey Client - java

I have a little problem here. When firing a request I want to sign the whole message with HMAC and add the resulting signature to the headers.
So I implemented
javax.ws.rs.ext.WriterInterceptorContext
In the
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException
method I cannot access the string representation of the entity. It always returns an empty String. The cause seems to be the MessageBodyWriter which is executed AFTER the WriterInterceptor.
Basically I have the following two scenarios failing:
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
try {
final ClientOutputStream stream = (ClientOutputStream) requestContext.getProperty(HTTPCLIENT_ENTITY_STREAM);
String payload = stream.getString(Charset.forName("UTF-8")); // returns alway empty String
String signature = doSomeSuffWithPayload(payload);
MultivaluedMap<String, Object> headers = context.getHeaders();
headers.add(HmacHeaderValue.X_SIGNATURE.headerName(), signature);
context.proceed();
} catch (IllegalArgumentException | ParseException | InvalidKeyException | NoSuchAlgorithmException ex) {
LOGGER.error(ex.getMessage());
} catch (UnsupportedEncodingException ex) {
LOGGER.error(ex.getMessage());
} catch (IOException ex) {
LOGGER.error(ex.getMessage());
}
}
Here the doSomeSuffWithPayload(payload) method does not work, because payload is always empty.
I thought a trick will do it, so I switched the context.proceed() call to anyother place:
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
try {
context.proceed();
final ClientOutputStream stream = (ClientOutputStream) requestContext.getProperty(HTTPCLIENT_ENTITY_STREAM);
String payload = stream.getString(Charset.forName("UTF-8")); // returns the right string representation
String signature = doSomeSuffWithPayload(payload);
MultivaluedMap<String, Object> headers = context.getHeaders();
headers.add(HmacHeaderValue.X_SIGNATURE.headerName(), signature); // doesn't add the header
} catch (IllegalArgumentException | ParseException | InvalidKeyException | NoSuchAlgorithmException ex) {
LOGGER.error(ex.getMessage());
} catch (UnsupportedEncodingException ex) {
LOGGER.error(ex.getMessage());
} catch (IOException ex) {
LOGGER.error(ex.getMessage());
}
}
In this case the string representation of the entity is ok. But adding the header to the request does not work.
So atm I can either have the (wrong) signature added to the headers and an always empty entity OR the right signature with the correct entity, but the header is not added.
My question is: Does anybody know a way to get the string representation of the entity by using the WriterInterceptor?
EDITH said:
We are using version 2.25.1 of jersey client. 2.27 didn't solve the problem either.

After searching deep in the API I found out that the entity gets indeed written after the WriterInterceptor in the MessageBodyWriter. Beside that the headers also get added during the process in the MessageBodyWriter. That's why both approaches above don't work.
My solution atm is to geht the right MessageBodyWriter and let it serialize the entity as it would do in the MessageBodyWriter that is executed after the WriterInterceptor. It this case the WriterInterceptor is not needed anymore, implementing ClientRequestFilter will do the trick.
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Providers;
#Context
private Providers providers;
private String getPayloadFromRequest(ClientRequestContext requestContext) throws IOException {
Object object = requestContext.getEntity();
if (object != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
// buffer into which myBean will be serialized
Class<Object> type = (Class<Object>) requestContext
.getEntityClass();
GenericType<Object> genericType = new GenericType<Object>(type) {
};
// get most appropriate MBW
final MessageBodyWriter<Object> messageBodyWriter = providers
.getMessageBodyWriter(type, type, new Annotation[]{},
MediaType.APPLICATION_JSON_TYPE);
try {
// use the MBW to serialize myBean into baos
messageBodyWriter.writeTo(object, object.getClass(),
genericType.getType(), new Annotation[]{},
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedHashMap<String, Object>(), baos);
} catch (IOException e) {
throw new RuntimeException(
"Error while serializing MyBean.", e);
}
return baos.toString();
} finally {
baos.close();
}
} else {
return "";
}
}
The code is not mine, but unfortunately I lost the source.

Related

How to rewrite POST request body on HttpServletRequest

I'm working on a Filter in which I have to get the request payload, decrypt it, check if it's a valid JSON and if it is go on with the chain and go to my service. The thing is that, so far I haven't been able to find a way to rewrite the body. Why I want to rewrite it? As the service expects a JSON and the request has an encrypted text in the body, once I decrypt it I want the body to be the decrypted JSON. Also, once I return from the service, I should rewrite the response to have the json encrypted. I've read a lot of forums and questions but couldn't get to a working solution.
Here's my code:
RequestLoginFilter.java
#WebFilter("/RequestLoginFilter")
public class RequestLoginFilter implements Filter{
protected final static Log logger = LogFactory.getLog(RequestLoginFilter.class);
private ServletContext context;
private CryptoUtil crypto;
public void init(FilterConfig fConfig) throws ServletException {
this.context = fConfig.getServletContext();
this.context.log("RequestLoggingFilter initialized");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// use wrapper to read multiple times the content
AuthenticationRequestWrapper req = new AuthenticationRequestWrapper((HttpServletRequest) request);
HttpServletResponse resp = (HttpServletResponse) response;
String payload = req.getPayload();
try {
String decryptedPayload = crypto.decrypt(payload);
JSONUtils.convertJSONStringToObject(decryptedPayload, LoginTokenTO.class);
} catch (GeneralSecurityException e) {
logger.error("Error when trying to decrypt payload '"+payload+"'");
throw new ServletException("Error when trying to decrypt payload '"+payload+"'", e);
}
chain.doFilter(req, resp);
System.out.println("a ver");
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
And also the wrapper, just in case:
AuthenticationRequestWrapper.java
public class AuthenticationRequestWrapper extends HttpServletRequestWrapper {
protected final static Log logger = LogFactory.getLog(AuthenticationRequestWrapper.class);
private final String payload;
public AuthenticationRequestWrapper (HttpServletRequest request) throws AuthenticationException {
super(request);
// read the original payload into the payload variable
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
// read the payload into the StringBuilder
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
// make an empty string since there is no payload
stringBuilder.append("");
}
} catch (IOException ex) {
logger.error("Error reading the request payload", ex);
throw new AuthenticationException("Error reading the request payload", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException iox) {
// ignore
}
}
}
payload = stringBuilder.toString();
}
#Override
public ServletInputStream getInputStream () throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(payload.getBytes());
ServletInputStream inputStream = new ServletInputStream() {
public int read ()
throws IOException {
return byteArrayInputStream.read();
}
};
return inputStream;
}
public String getPayload() {
return payload;
}
}
Hopefully somebody here knows how I can get to get this working.
Thanks in advance guys.
Whilst what you are asking is probably technically possible, it doesn't sound like the right approach to me.
What you need is a security layer that sits between the incoming request (endpoint) and your service. Re-writing the body of the request is a strange thing to be doing (which probably explains why you're having issues). Is there a reason you want this to be done in a Filter? After all, filters are designed to filter requests, not rewrite them ;)
A more logical/transparent solution would be to have your endpoint accept all incoming requests, decrypt and validate them before passing the request onto your service tier. Something like this:
public void handleRequest(Request request) {
try {
IncomingRequest x = securityManager.decrypt(request);
Response r = myService.handleRequest(x);
handleResponse(securityManager.encrypt(r));
}catch(InvlidateMessage x) {
handleInvalidMessage...
}catch(BusinessException x) {
handleBusinessException...
}
}

How to retrieve a mapped exception from jersey?

I've used an ExceptionMapper on the server side, putting the custom exception in the Response's body. How can I retrieve the original exception on the client side, and throw it to the caller?
You can serialize the exception and include it as a part of the response:
public final class SerializingExceptionMapper implements ExceptionMapper<Exception> {
#Override
public Response toResponse(Exception exception) {
try {
final byte[] serializedException = serializeException(exception);
final String base64EncodedException = Base64.getEncoder().encodeToString(serializedException);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new Message(base64EncodedException))
.build();
} catch (Exception ex) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
private byte[] serializeException(Exception ex) throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(ex);
oos.close();
return bos.toByteArray();
}
}
final class Message {
public Message(String exception) {
this.exception = exception;
}
public String exception;
}
Then on the client side you should do just the opposite:
Unmarshal the (maybe JSON) response
Decode the base64 encoded exception to a byte[]
De-serialize the exception
Create a ByteArrayInputStream
Create ObjectInputStream
Just readObject() the exception
Do whatever you want with it on the client side
PS: This can be achieved without any buffering (i.e. without the byte[]s) -> just use a StreamingOutput as .entity() and write to the provided output stream instead of a ByteArrayOutputStream. The same applies for deserialization on the client side.

Apache CXF ExceptionMapper with request message

I was wondering if something like this is possible or if there's a better way to do it:
public class WebServiceExceptionMapper implements ExceptionMapper<Exception> {
private static Logger logger = LoggerFactory.getLogger(WebServiceExceptionMapper.class);
#Override
public Response toResponse(Exception ex, Message requestMessage) {
logger.error("request=" + httpMessage.etc..etc.., ex);
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
}
Basically I would like to be able to log not just the exception but the request message that triggered the error. More specifically if the request contains a JSON or XML I would like to grab that.
NVM. Figured it out by injecting MessageContext in my ExceptionMapper and adding a custom RequestHandler like described here: http://cxf.547215.n5.nabble.com/Accessing-JAXRS-JSON-content-directly-td4390185.html
public class StringContentRequestHandler implements RequestHandler {
private static final Logger logger = LoggerFactory.getLogger(RawContentRequestHandler.class);
public Response handleRequest(Message m, ClassResourceInfo resourceClass) {
InputStream is = m.getContent(InputStream.class);
try {
CachedOutputStream bos = new CachedOutputStream();
IOUtils.copy(is, bos);
bos.flush();
is.close();
m.setContent(InputStream.class, bos.getInputStream());
StringBuilder builder = new StringBuilder();
bos.writeCacheTo(builder, "utf-8");
m.setContent(String.class, builder.toString());
return null;
} catch (IOException ex) {
logger.error("IOException on getting raw content", ex);
return null;
}
}
}

IllegalStateException on redirect

I have a PhaseListener which listens on phaseId RENDER_RESPONSE. This faceListener calls this method:
public void doLogin(ServletRequest request) throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
HttpServletRequest req = (HttpServletRequest) request;
String code = req.getParameter("code");
if (StringUtil.isNotBlankString(code)) {
String authURL = Facebook.getAuthURL(code);
URL url = new URL(authURL);
try {
....
if (accessToken != null && expires != null) {
boolean isLoginOk = service.authFacebookLogin(accessToken);
if (isLoginOk) {
fc.getApplication().getNavigationHandler().handleNavigation(fc, "/welcome.xhtml", "logged-in");
}
} else {
throw new RuntimeException("Access token and expires not found");
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (FacebookException e) {
Logger.getLogger(FBOauth.class.getName()).log(Level.SEVERE, "Facebook error", e);
}
}
}
private String readURL(URL url) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = url.openStream();
int r;
while ((r = is.read()) != -1) {
baos.write(r);
}
return new String(baos.toByteArray());
}
When it redirects I get the following exception which I cant really find any solution to. From what I understand it is thrown because response is already comitted but why is it already comitted?
java.lang.IllegalStateException at
org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:522)
at com.sun.faces.context.ExternalContextImpl.redirect(ExternalContextImpl.java:572)
at com.sun.faces.application.NavigationHandlerImpl.handleNavigation(NavigationHandlerImpl.java:182)
at wmc.web.facebook.FBOauth.doLogin(FBOauth.java:57)
at wmc.web.listeners.FacebookSignInListener.afterPhase(FacebookSignInListener.java:56)
at com.sun.faces.lifecycle.Phase.handleAfterPhase(Phase.java:189)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:107)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:313)
By the way I really appreciate all the help I get in here :)
Your phaselistener is apparently hooking on afterPhase of RENDER_RESPONSE. It's too late to change the response then. The response is already been sent to the client. Rather hook on beforePhase() of the `RENDER_RESPONSE.
My guess would be that before the sendRedirect() is executed some part of your code has already streamed text to the servlet repsonse, maybe some generic information that is send to all responses?

Is it possible to access the raw SOA/XML message in a JAX-RPC java client?

I am trying to access the XML response via a JAX-RPC java client.
I have been looking into Axis custom handlers, but it looks like they are useful only in the service side.
Here's some code that will give you the XML response payload back. You can either get it directly from AXIS Stub class, or from a handler that wrote it to the MessageContext. Here's the one that reads it directly:
private String getSOAPResponseXML(Object clientstub) {
String returnValue = null;
org.apache.axis.client.Stub stub = (org.apache.axis.client.Stub)clientstub;
Call call = stub._getCall();
if (call != null) {
MessageContext ctx = call.getMessageContext();
// If I registered a handler
// returnValue = (String) ctx.getProperty( ClientHandler.SOAP_RESPONSE );
// or use:
try {
Message msg = call.getResponseMessage();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// NOTE: If we never get a response (a request handler throws an uncaught error
// this can cause a java.lang.NullPointerException
msg.writeTo(baos);
returnValue = baos.toString();
} catch (java.io.IOException ex) {
log.debug("Error in getSOAPResponseXML", ex);
} catch (javax.xml.soap.SOAPException ex) {
log.debug("Error in getSOAPResponseXML", ex);
}
}
return returnValue;
} // getSOAPResponseXML
If you need the handler, just let me know.

Categories

Resources