JBoss Community Archive (Read Only)

PicketBox

Securing JAX-RS Payload

In this article, we are going to show how to perform encryption on a JSON payload coming from a JAX-RS endpoint.  We rely on the excellent JAX-RS implementation from JBoss community called as RESTEasy.

Recommended Approach For Signature/Encryption in RESTEasy

When the user has complete control over the client and server infrastructure (ie. end to end), then the recommended approach is to use S/MIME in RESTEasy.

http://docs.jboss.org/resteasy/docs/2.3.4.Final/userguide/html/ch38.html

If there is a need to support JOSE (JSON Object Signing and Encryption) in your use cases, then read on.

Source Repository

https://github.com/picketbox/picketbox-jaxrs/

If you want to contribute, do not forget to fork into your github account.

RESTEasy Example

Assume that I have the following jax-rs class called "Resource".

Resource.java
package org.picketbox.test.jaxrs;

import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

@Path("/bookstore")
public class Resource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public String hello(@Context HttpServletResponse response) {
        response.setContentType(MediaType.APPLICATION_JSON);
        return "{\"name\":\"Harry Potter\"}";
    }

    @GET
    @Path("/books")
    public String getBooks() {
        return "books=Les Miserables";
    }

    @GET
    @Path("/book/{isbn}")
    public String getBook(@PathParam("isbn") String id) {
        return "11123";
    }
}

The default method is hello which does take in an injection of  a HttpServletResponse object.  This is very important for JSON payload encryption because we want to set the content type to "application/json" in the response.

Also there is another method getBooks that just returns a string value (Not JSON).

The JAXRS Application class is below:

TestApplicationConfig.java
package org.picketbox.test.jaxrs;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.core.Application;

public class TestApplicationConfig extends Application {
    private static Set services = new HashSet();
    public  TestApplicationConfig() {
        // initialize restful services
        services.add(new Resource());
    }
    @Override
    public  Set getSingletons() {
        return services;
    }
    public  static Set getServices() {
        return services;
    }
}

Configure the web.xml as follows:

web.xml

<web-app xmlns:javaee="http://java.sun.com/xml/ns/javaee"
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<context-param>
		<param-name>resteasy.scan</param-name>
		<param-value>true</param-value>
	</context-param>

	<context-param>
		<param-name>resteasy.resources</param-name>
		<param-value>org.picketbox.test.jaxrs.Resource</param-value>
	</context-param>
	<context-param>
		<param-name>javax.ws.rs.Application</param-name>
		<param-value>org.picketbox.test.jaxrs.TestApplicationConfig</param-value>
	</context-param>

	<!-- set this if you map the Resteasy servlet to something other than /* -->
	<context-param>
		<param-name>resteasy.servlet.mapping.prefix</param-name>
		<param-value>/rest</param-value>
	</context-param>


	<!-- if you are using Spring, Seam or EJB as your component model, remove
		the ResourceMethodSecurityInterceptor <context-param> <param-name>resteasy.resource.method-interceptors</param-name>
		<param-value> org.jboss.resteasy.core.ResourceMethodSecurityInterceptor </param-value>
		</context-param> -->

	<listener>
		<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
	</listener>

	<filter>
		<filter-name>JWE</filter-name>
		<filter-class>org.picketbox.jaxrs.filters.JWEInterceptor</filter-class>
		<init-param>
			<param-name>keystore</param-name>
			<param-value>keystore/pbox_jaxrs.keystore</param-value>
		</init-param>
		<init-param>
			<param-name>storepass</param-name>
			<param-value>pass123</param-value>
		</init-param>
	</filter>

	<filter-mapping>
		<filter-name>JWE</filter-name>
		<url-pattern>/rest/*</url-pattern>
	</filter-mapping>

	<servlet>
		<servlet-name>Resteasy</servlet-name>
		<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>Resteasy</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>
</web-app>

Assuming you have everything set up as a Web Archive (WAR) and know how to package it as a RESTEasy application, we can take a look at the JUnit Test Class.

JUnit Test Class (RESTEasy with JSON Encryption on Jetty Embedded Web Server):

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.picketbox.test.jaxrs;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher;
import org.json.JSONObject;
import org.junit.Test;
import org.mortbay.jetty.servlet.ServletHolder;
import org.mortbay.jetty.webapp.WebAppContext;
import org.picketbox.core.util.KeyStoreUtil;
import org.picketbox.jaxrs.filters.JWEInterceptor;
import org.picketbox.json.token.JSONWebToken;
import org.picketbox.test.http.jetty.EmbeddedWebServerBase;

/**
 * Unit test RESTEasy integration with PicketBox
 * @author anil saldhana
 * @since Aug 2, 2012
 */
public class RESTEasyStandaloneTestCase extends EmbeddedWebServerBase{

    @Override
    protected void establishUserApps() {
        ClassLoader tcl = Thread.currentThread().getContextClassLoader();
        if (tcl == null) {
            tcl = getClass().getClassLoader();
        }

        final String WEBAPPDIR = "resteasy/standalone";

        final String CONTEXTPATH = "/*";

        // for localhost:port/admin/index.html and whatever else is in the webapp directory
        final URL warUrl = tcl.getResource(WEBAPPDIR);
        final String warUrlString = warUrl.toExternalForm();

        WebAppContext context = new WebAppContext(warUrlString, CONTEXTPATH);

        context.setContextPath("/");
        ServletHolder servletHolder = new ServletHolder(new HttpServletDispatcher());
        servletHolder.setInitParameter("javax.ws.rs.Application", TestApplicationConfig.class.getName());
        context.addServlet(servletHolder, "/*");

        //context.setParentLoaderPriority(true);
        server.setHandler(context);
    }

    /**
     * This testcase tests that a regular non-json payload is returned without
     * any encryption
     * @throws Exception
     */
    @Test
    public void testPlainText() throws Exception{

        String urlStr = "http://localhost:11080/rest/bookstore/books";
        URL url = new URL(urlStr);

        DefaultHttpClient httpclient = null;
        try {

            httpclient = new DefaultHttpClient();

            HttpGet httpget = new HttpGet(url.toExternalForm());

            httpget.setHeader(JWEInterceptor.CLIENT_ID, "1234");

            System.out.println("executing request:" + httpget.getRequestLine());
            HttpResponse response = httpclient.execute(httpget);
            HttpEntity entity = response.getEntity();

            System.out.println("----------------------------------------");
            StatusLine statusLine = response.getStatusLine();
            System.out.println(statusLine);
            if (entity != null) {
                System.out.println("Response content length: " + entity.getContentLength());
            }

            InputStream is = entity.getContent();
            String contentString = getContentAsString(is);
            System.out.println("Plain Text=" + contentString);
            assertNotNull(contentString);
            assertEquals("books=Les Miserables", contentString);

            assertEquals(200, statusLine.getStatusCode());
            EntityUtils.consume(entity);
        } finally {
            // When HttpClient instance is no longer needed,
            // shut down the connection manager to ensure
            // immediate deallocation of all system resources
            httpclient.getConnectionManager().shutdown();
        }
    }

   /**
     * This test case tests the encryption of JSON payload
     * @throws Exception
     */
    @Test
    public void testJAXRS_jsonEncryption() throws Exception{

        PrivateKey privateKey = getPrivateKey();

        String urlStr = "http://localhost:11080/rest/bookstore/";
        URL url = new URL(urlStr);

        DefaultHttpClient httpclient = null;
        try {

            httpclient = new DefaultHttpClient();

            HttpGet httpget = new HttpGet(url.toExternalForm());

            httpget.setHeader(JWEInterceptor.CLIENT_ID, "1234");

            System.out.println("executing request:" + httpget.getRequestLine());
            HttpResponse response = httpclient.execute(httpget);
            HttpEntity entity = response.getEntity();

            System.out.println("----------------------------------------");
            StatusLine statusLine = response.getStatusLine();
            System.out.println(statusLine);
            if (entity != null) {
                System.out.println("Response content length: " + entity.getContentLength());
            }

            InputStream is = entity.getContent();
            String contentString = getContentAsString(is);

            JSONWebToken jwt = new JSONWebToken();
            jwt.setPrivateKey(privateKey);
            jwt.decode(contentString);

            JSONObject jsonObject = jwt.getData();

            assertNotNull(jsonObject);
            assertEquals("Harry Potter", jsonObject.getString("name"));
            System.out.println(jsonObject.toString());

            assertEquals(200, statusLine.getStatusCode());
            EntityUtils.consume(entity);
        } finally {
            // When HttpClient instance is no longer needed,
            // shut down the connection manager to ensure
            // immediate deallocation of all system resources
            httpclient.getConnectionManager().shutdown();
        }
    }

    private String getContentAsString(InputStream is) throws IOException{
        //read it with BufferedReader
        BufferedReader br
            = new BufferedReader(
                new InputStreamReader(is));

        StringBuilder sb = new StringBuilder();

        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();
        return sb.toString();
    }

    private PrivateKey getPrivateKey() throws Exception{
        InputStream is = getClass().getClassLoader().getResourceAsStream("keystore/pbox_jaxrs.keystore");
        assertNotNull(is);
        KeyStore keystore = KeyStoreUtil.getKeyStore(is, "pass123".toCharArray());

        // Get private key
        Key key = keystore.getKey("1234", "pass123".toCharArray());
        return (PrivateKey) key;
    }
}

Generate a KeyStore

$ keytool -genkey -alias 1234 -keyalg RSA -keysize 1024 -keystore pbox_jaxrs.keystore
Enter keystore password: pass123
Re-enter new password: pass123

What is your first and last name?
  [Unknown]:  PicketBox JAXRS
What is the name of your organizational unit?
  [Unknown]:  PicketBox
What is the name of your organization?
  [Unknown]:  JBoss
What is the name of your City or Locality?
  [Unknown]:  Chicago
What is the name of your State or Province?
  [Unknown]:  IL
What is the two-letter country code for this unit?
  [Unknown]:  US
Is CN=PicketBox JAXRS, OU=PicketBox, O=JBoss, L=Chicago, ST=IL, C=US correct?
  [no]:  
What is your first and last name?
  [PicketBox JAXRS]:  
What is the name of your organizational unit?
  [PicketBox]:  
What is the name of your organization?
  [JBoss]:  
What is the name of your City or Locality?
  [Chicago]:  
What is the name of your State or Province?
  [IL]:  
What is the two-letter country code for this unit?
  [US]:  
Is CN=PicketBox JAXRS, OU=PicketBox, O=JBoss, L=Chicago, ST=IL, C=US correct?
  [no]:  yes
Enter key password for <1234>
    (RETURN if same as keystore password):
Note that you can also give the store password and key password via the   "-storepass pass123   -keypass pass123"  options while creating the keystore.

Configuration of JWEInterceptor

JWEInterceptor is the filter that applies the JSON Encryption on the outgoing JSON payload.  It requires access to a keystore as shown above.

The request has a header variable called as "CLIENT_ID" that uniquely identifies the client who is interacting with the JAXRS application.  This client id is what is used as alias to obtain the public key for encryption.

On the client side, the client id can be used as an alias to obtain the private key from a local keystore.

NOTE:  The Client with ClientID should have registered with the server by giving their public keys. (Out of band interaction)

Future Work

  • Showcase use of JSON Web Signature to protect integrity of the payload.

JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-11 12:16:27 UTC, last content change 2012-08-06 15:21:56 UTC.