I was asked to add an extra encryption to json formatted data exchanged between clients and a RESTful web service.
Existing Design
The RESTful web service was implemented in a JEE 5 Spring-based web application.
Data were sent by clients in the body of POST
requests, while others data
were returned by the RESTful web service in the body response.
The headers of a typical clients POST
request were:
Accept=application/json
Content-Type=application/json
The request was mapped to the web service class method by the @RequestMapping
annotation:
1 2 3 4 5 6 7 8 9 10 |
|
The @ResponseBody
annotation bounds data to the body response. Under the
cover the
content negotiation
transform data from the DataTransferObject
to json.
The communication between a client and the RESTful web service can be represented as follows:
-- -- | | | Content-Type | +--------------------request-------------------->| | application/json | c | | s l | | e i | | r e | | v n | | e t | | r | | | Accept | |<-------------------response--------------------+ | application/json | | | -- --
Because clients expected to exchange a string representation of the encrypted
data they changed their request header to text/plain
:
Designing An Encryption Filter
I thought I should have used an implementation of the filter pattern because each request has to be:
- decrypted for the web service
- processed by the web service
- encrypted for the client
The first implementation of the filter pattern which comes to mind was the
HandlerInterceptor
,
but I didn’t want to change to text/plain
the @RequestMapping
annotation
of all web service methods, and because I used the @ResponseBody
too:
Note that the postHandle method of HandlerInterceptor is not always ideally suited for use with @ResponseBody and ResponseEntity methods. In such cases an HttpMessageConverter writes to and commits the response before postHandle is called which makes it impossible to change the response, for example to add a header. Instead an application can implement ResponseBodyAdvice and either declare it as an @ControllerAdvice bean or configure it directly on RequestMappingHandlerAdapter.
Therefore, in the end I opted for the JEE servlet filter implementation because I would have to rewrite:
- request header from
text/plain
toapplication/json
- response header from
application/json
totext/plain
- request body from encrypted to decrypted data
- response body from decrypted to encrypted data
-- --- -- | | | | | Content-Type | | Content-Type | +--------------request----------->| +--------------request----------->| | text/plain | | application/json | | | | | c | | f | | s l | | i | | e i | | l | | r e | | t | | v n | | e | | e t | | r | | r | | | | | Accept | | Accept | |<-------------response-----------+ |<-------------response-----------+ | text/plain | | application/json | | | | | -- --- --
leaving the RESTful web service implementation classes untouched.
Implementation Details
The main part of the filter starts decrypting data, then it passes to the chain of responsibility (at the end of which there is the RESTful web service), finally it encrypts data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
The interesting part is about wrapping HttpServletRequest
and
HttpServletResponse
such it is possible:
- rewrite request header from
text/plain
toapplication/json
- rewrite response header from
application/json
totext/plain
- rewriting decrypted data in the request before passing it to RESTful web service
reading encrypted data from the response in order to write them in the servlet response for the clientreading clear data from the response wrapper in order to encrypt them for the client
The first point was achieved overwriting getHeader
and getHeaders
methods: they “intercept” the Accept
and the Content-Type
headers and
they replace from text/plain
to application/json
.
The third point was achieved using a ByteArrayInputStream
to store data
written in the request. Such approach is needed because servlet request
can only be read (from) and not written (into).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
The above points 2. and 4. were achieved in a similar (dual) way to the one
used for points 1. and 3. In particular, a PrintWriter
is used to store
clear data read by the encryption servelt filter in order to encrypt
them for the client. Such approach is needed because servelt response can
only be written (into) and not read (from).
4 Comments to “Adding Encryption to a RESTful Web Service”
Posted by Jack #
Is the full source code on github somewhere?
Posted by Tarin Gamberini #
Dear Jack,
Unfortunately not yet, sorry.
I hope to have enough time in the future to write an update to this post.
Best regards,
Tarin
Posted by Jack #
Getting the request body to decrypt is straight forward, but how do you get just the response “body” back to encrypt? Are you using a regex to pull the body out of the response content to encrypt it and then doing a string replace to put the encrypted portion back into the response content?
Posted by Tarin Gamberini #
Dear Jack,
Sorry for the late answer but I was on holiday. I have updated the above point 4 because preparing this answer I have found it not so clear as it seems when I wrote it.
You can’t read the response body, and you can’t write the request body. You can work around these limitations, for example, adding a
PrintWriter
to a response wrapper, and adding anInputStream
to a request wrapper. I didn’t use any regex. I used aPrintWriter
to store clear data inresponseWrapper
, in particularresponseWrapper
holds clear data afterchain.doFilter(requestWrapper, responseWrapper)
has been called.Than in the
HttpServletResponseReadableWrapper
create a method which returns clear data you get from thePrintWriter
. This method works around the above-mentioned limitation making readable your response wrapper.You can find useful information studying “How can I read an HttpServletReponses output stream?” and “How to log response content from a java web server”
Kind regards,
Tarin
Post a comment
A comment is submitted by an ordinary e-mail. Your e-mail address will not be published or broadcast.
This blog is moderated, therefore some comments might not be published. Comments are usually approved by the moderator in one/three days.