Tarin Gamberini

A software engineer and a passionate java programmer

Adding Encryption to a RESTful Web Service

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
@RequestMapping(
    value = "/my/rest/resource/{id}.json",
    method = RequestMethod.POST,
    headers = "Accept=" + MediaType.APPLICATION_JSON_VALUE,
    consumes = {MediaType.APPLICATION_JSON_VALUE},
    produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody
    ResponseEntity<DataTransferObject> myMethod(...) {
        ...
}

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:

  1. decrypted for the web service
  2. processed by the web service
  3. 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 to application/json
  • response header from application/json to text/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
public void doFilter(ServletRequest servletReq, ServletResponse servletResp,
        FilterChain chain) throws IOException, ServletException {
    ...
    byte[] decryptedData = decrypt(request, ...);

    HttpServletRequestWritableWrapper requestWrapper =
        new HttpServletRequestWritableWrapper(
            request, decryptedData);

    HttpServletResponseReadableWrapper responseWrapper
        = new HttpServletResponseReadableWrapper(
            response);

    chain.doFilter(requestWrapper, responseWrapper);

    String encryptedData = encrypt(responseWrapper, ...);
    servletResp.getWriter().write(encryptedData);
}

The interesting part is about wrapping HttpServletRequest and HttpServletResponse such it is possible:

  1. rewrite request header from text/plain to application/json
  2. rewrite response header from application/json to text/plain
  3. rewriting decrypted data in the request before passing it to RESTful web service
  4. reading encrypted data from the response in order to write them in the servlet response for the client reading 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
class HttpServletRequestWritableWrapper extends HttpServletRequestWrapper {

    private final ByteArrayInputStream decryptedDataBAIS;

    HttpServletRequestWritableWrapper(HttpServletRequest request, byte[] decryptedData) {
        super(request);
        decryptedDataBAIS = new ByteArrayInputStream(decryptedData);
    }

    @Override
    public String getHeader(String headerName) {
        String headerValue = super.getHeader(headerName);
        if ("Accept".equalsIgnoreCase(headerName)) {
            return headerValue.replaceAll(
                MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE);
        } else if ("Content-Type".equalsIgnoreCase(headerName)) {
            return headerValue.replaceAll(
                MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE);
        }
        return headerValue;
    }

    @Override
    public Enumeration getHeaders(final String headerName) {
        ...
    }

    @Override
    public String getContentType() {
        String contentTypeValue = super.getContentType();
        if (MediaType.TEXT_PLAIN_VALUE.equalsIgnoreCase(contentTypeValue)) {
            return MediaType.APPLICATION_JSON_VALUE;
        }
        return contentTypeValue;
    }

    @Override
    public BufferedReader getReader() throws UnsupportedEncodingException {
        return new BufferedReader(new InputStreamReader(decryptedDataBAIS, UTF_8));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new ServletInputStream() {
            @Override
            public int read() {
                return decryptedDataBAIS.read();
            }
        };
    }

}

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 # 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 # 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 an InputStream to a request wrapper. I didn’t use any regex. I used a PrintWriter to store clear data in responseWrapper, in particular responseWrapper holds clear data after chain.doFilter(requestWrapper, responseWrapper) has been called.

Than in the HttpServletResponseReadableWrapper create a method which returns clear data you get from the PrintWriter. 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.