Filters

JAX-RS filters are available on the client and the container APIs, the packages javax.ws.js.client and javax.ws.js.container.

JAX-RS filters

On the client side, there are two types of filter, namely the Java interface ClientRequestFilter and ClientResponseFilter . For each direction, there is a corresponding context interface ClientRequestContext and ClientResponseContext.

On the server side, there are two types of filter, namely the Java interface ContainerRequestFilter and ContainerResponseFilter . For each direction, there is a corresponding context interface ContainerRequestContext and ContainerResponseContext.

Let us look at the server side filter as a start.

Server-side filters

JAX-RS executes the ContainerRequestFilter filter before invoking the wrapped target resource. JAX-RS executes ContainerResponseFilter after invoking the wrapped target resource.

The category of ContainerRequestFilter is divided into two more filter styles. A filter can be pre-matching or post-matching. The default is the post-matching. Pre-matching filters are designed to modify request attributes and header attributes before the JAX-RS runtime perform path pattern matching on the URI resource.

In order to designate a ContainerRequestFilter is a pre-matching filter, the class must be annotated with @javax.ws.js.container.PreMatching.

If we wanted to copy the HTTP Header parameter User Agent and shadow it for a processing pipeline, we could write a filter in the following way:

package je7hb.jaxrs.basic;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.ext.Provider;
import java.io.IOException;

@Provider
@PreMatching
public class AddExtraUserAgentFilter implements ContainerRequestFilter {
  @Override
  public void filter(ContainerRequestContext context)
  throws IOException {
    String userAgent = context.getHeaderString("User-Agent");
    if ( userAgent != null ) {
      context.getHeaders().putSingle("X-User-Agent-Copy", userAgent );
    }
  }
}

The filter AddExtraUserAgentFilter is annotated as @javax.ws.js.ext.Provider. The class implements ContainerRequestFilter and the method filter(). We look up the header parameter by name from the supplied context. Since this agent usually is supplied by the client, we can make a copy of the parameter into a new header key and value pair called X-User-Agent-Copy.

If we wanted to, the flexibility of the JAX-RS API, allows us to change the User-Agent string. Since this filter is annotated with @PreMatching then the runtime will invoke this filter before proceeding with the URI path pattern matching phase and before the target resource is invoked.

Suppose we wanted to have a filter that automatically added an expiration time to the HTTP Response header for any JAX-RS Resource. We could write a container response filter like the following code:

package je7hb.jaxrs.basic;
import javax.ws.rs.container.*;
import javax.ws.rs.ext.Provider;
/* ... imports omitted */

@Provider
public class AutoExpirationDateFilter implements ContainerResponseFilter{
  private static SimpleDateFormat formatter =new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
  @Override
  public void filter(ContainerRequestContext reqCtx,ContainerResponseContext resCtx)
  throws IOException {
    if ( reqCtx.getMethod().equals("GET")) {
      Date oneHour = new Date(System.currentTimeMillis() + 60* 1000 );
      resCtx.getHeaders().add("Expires",formatter.format( oneHour));
    }
  }
}

In the class AutoExpirationDateFilter which implements the contract from ContainerResponseFilter, the filter() method accepts two context parameters and it is extremely useful to have access to both the request and response context objects.

We only add the expiration response header field for HTTP GET request, so in the method we can check this situation. With the response context, we add the expiration header with a properly formatted date and timestamp.

Client-side filters

Writing JAX-RS filters for the client side is a one step filter, because there is no URI path pattern matching occurring. The developer has a choice of two filter types, namely, ClientRequestFilter and ClientResponseFilter . The JAX-RS filter will invoke ClientRequestFilter just before the HTTP request is sent to the remote URI resource. Similarly, after the remote URI resource processes the data and sends back a response, then the JAX-RS runtime invokes ClientResponseFilter instance.

We shall now inspect the code for a useful bit of kit in our toolkit. What happens if we have problem with some production code involving JAX-RS? Would it not be nice to debug to a standard console (and perhaps later to a logging facility) the requests going from the client to the remote URI resource and reading the server response? It would be nice to have a master client that we can travel around the business and validate the communication is functioning correctly between the client and server.

So the following is the basis of a debuggable logger for JAX-RS client, albeit incomplete:

package je7hb.jaxrs.basic;
import javax.ws.rs.client.*;
import javax.ws.rs.ext.Provider;
/* ... imports omitted */

@Provider
public class DebugClientLoggingFilter
implements ClientRequestFilter, ClientResponseFilter {

  @Override
  public void filter(ClientRequestContext reqCtx) 
  throws IOException {
    System.out.printf("**** DEBUG CLIENT REQUEST ****
");
    System.out.printf("uri: %s
", reqCtx.getUri());
    if ( reqCtx.getEntity() != null ) {
      System.out.printf("entity: %s
",
      reqCtx.getEntity().getClass().getName() + "@" +Integer.toHexString(System.identityHashCode( reqCtx.getEntity())));
    }
    System.out.printf("method: %s
", reqCtx.getMethod());
    System.out.printf("mediaType: %s
",reqCtx.getMediaType());
    System.out.printf("date: %s
", reqCtx.getDate());
    System.out.printf("language: %s
", reqCtx.getLanguage());
    for (String name: reqCtx.getHeaders().keySet()) {
      System.out.printf("header[%s] => %s
",name, reqCtx.getHeaderString(name) );
    }
    for (String name: reqCtx.getCookies().keySet()) {
      System.out.printf("cookie[%s] => %s
",name, reqCtx.getHeaderString(name) );
    }
    System.out.printf("**** END CLIENT REQUEST ****

");
  }
  // ... incoming filter method }

This class DebugClientLoggingFilter implements both the client request and response filters. As you can see the two different context objects ClientRequestContext and ClientResponseContext provide a wealth of information.

From the client, we are able to find out the request URI, the entity, the method, media type, language, headers, and cookies. Similarly, from the server we can debug the status, status code, response length, the date, headers, and cookies.

Once the remote endpoint has serviced the request, we expect a response, which can also be filtered. The following is the other incoming implementation filter method:

@Override
public void filter(ClientRequestContext reqCtx,ClientResponseContext resCtx) 
throws IOException {
  System.out.printf("**** DEBUG CLIENT RESPONSE ****
");
  System.out.printf("status: %s
", resCtx.getStatus());
  System.out.printf("status info: %s
", resCtx.getStatusInfo());
  System.out.printf("length: %s
", resCtx.getLength());
  System.out.printf("mediaType: %s
", resCtx.getMediaType());
  System.out.printf("date: %s
", resCtx.getDate());
  System.out.printf("language: %s
", resCtx.getLanguage());
  for (String name: resCtx.getHeaders().keySet()) {
    System.out.printf("header[%s] => %s
",name, resCtx.getHeaderString(name) );
  }
  for (String name: resCtx.getCookies().keySet()) {
    System.out.printf("cookie[%s] => %s
",name, resCtx.getHeaderString(name) );
  }
  System.out.printf("**** END CLIENT RESPONSE ****

");
}

We have access to the response header, content type, length, data, and also cookies. To find out more information, it is worth your while examining the API in detail for both ClientRequestContext and ClientResponseContext.

To configure the filter from the unit test, we set up the ClientBuilder in the following way:

@Test
public void shouldRetrieveBookList() throws Exception {
  WebTarget target = ClientBuilder.newClient()
  .register(new DebugClientLoggingFilter())
  .target("http://localhost:8080/mywebapp/rest/async/books");
  Future<Response> future = target.request().async().get();
    /* ... */
}

DebugClientLoggingFilter is registered on the builder object.

The following is a screenshot of the unit test in action:

Client-side filters

If you are going to unit test the server response in an application, why would you not choose the JAX-RS client side library? It is a no brainer.

We shall move on to entity interceptors.

JAX-RS interceptors

Inceptors handle message bodies, the actual payload of the request and response to the remote JAX-RS resource. Entity interceptors are executed in the call stack frame as their corresponding reader or writer, which means there are involved in the same Java thread.

There are two types of interceptors, namely, javax.ws.rs.ext.ReaderInterceptor and javax.ws.rs.ext.WriterInterceptor. The reader interceptor is designed to wrap around the execution of the javax.ws.rs.ext.MessageBodyReader types. The writer interceptor is designed to wrap around the execution of javax.ws.rs.ext.MessageBodyWriter.

Why would developers want to create an interceptor? One circumstance may be to provide encryption and destruction around a particular resource type of data. Another idea would be generate secure digital signatures for any type of output.

Here is an example of both ReaderInterceptor and WriterInterceptor that performs AES encryption and decryption:

package je7hb.jaxrs.basic;
import javax.crypto.*;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.*;
import java.io.*;

@Provider 
public class AESCipherInterceptor 
implements ReaderInterceptor, WriterInterceptor{
  private final AESCipher cipher;

  public AESCipherInterceptor() throws Exception {
    final byte[] salt ={ 1,2,4,8,16,32,64,-64,-32,-16,-8,-4,-2,-1};
    final String password = «java1995»;
    cipher = new AESCipher(password, salt);
  }

  @Override
  public Object aroundReadFrom(ReaderInterceptorContext context )
  throws IOException, WebApplicationException {
    InputStream old = context.getInputStream();
    context.setInputStream( new CipherInputStream(old, cipher.getDecryptCipher()));
    try {
      return context.proceed();
    }
    finally {
      context.setInputStream(old);
    }
  }

  @Override
  public void aroundWriteTo(WriterInterceptorContext context )
  throws IOException, WebApplicationException {
    OutputStream old = context.getOutputStream();
    context.setOutputStream( new CipherOutputStream(old, cipher.getEncryptCipher()));
    try {
      context.proceed();
      context.getHeaders().add("X-Encryption", "AES");
    }
    finally {
      context.setOutputStream(old);
    }
  }
}

I should say immediately as a professional developer you never expose the security credentials to hacking in source code. The password and salt would be securely obtained by proper means through a secure channel.

Note

In order to be truly secure in the communication, first, you could make the connection protocol SSL. Second, ensure the plain text of the password is never passed in the stream and share the password verbally, orally in a face-to-face meeting. Third, generate the salt using javax.security.SecureRandom. Share the salt in an out-of-band communication between the server and the client in an initial hand shaking mechanism.

The annotation @Provider is applied to the interceptor class AESCipherInterceptor and therefore JAX-RS server side runtime becomes aware of its existence.

The class AESCipherInterceptor uses a helper class AESCipher to delegate the business of configuration key generator, cipher streams and the business of configuring AES in secure Java. This leaves the two implementation methods aroundReadFrom() and aroundWriteTo() relatively free of clutter.

The style of programming for both of these methods follows that of Aspect Oriented Programming (AOP). We temporarily replace the input or output stream, before invoking the target in the context. After the invoked method returns we restore the previous stream. We must surround the invocation with a try-finally block to ensure the restoration always happens regardless of the normal or abnormal termination of the target method.

We add an additional header to the response output in the aroundWriteTo() method.

The following is the code for the delegate class, AESCipher:

package je7hb.jaxrs.basic;
/* ... imports omitted */

public class AESCipher {
  private final KeyGenerator keyGen;
  private final Cipher encryptCipher, decryptChipher;

  public Cipher getEncryptCipher() { return encryptCipher;}
  public Cipher getDecryptCipher() { return decryptCipher;}

  public AESCipher( String passwordText, final byte[] salt )
  throws Exception {keyGen = KeyGenerator.getInstance("AES");
    final char[] password = passwordText.toCharArray();
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
    SecretKey tmp = factory.generateSecret(spec);
    SecretKey aesKey = new SecretKeySpec(tmp.getEncoded(), "AES");

    encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec);

    decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
  }

  public byte[] encrypt( String plainText ) {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream,encryptCipher);
    try {
      cipherOutputStream.write(plainText.getBytes());
      cipherOutputStream.flush();
      cipherOutputStream.close();
      return outputStream.toByteArray();
    }
    catch (Exception e) {
      e.printStackTrace(System.err);
      return null;
    }
  }

  public String decrypt( byte[] cipherText ) {
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    ByteArrayInputStream inputStream = new ByteArrayInputStream(cipherText);
    CipherInputStream cipherInputStream = null;
    try {
      cipherInputStream = new CipherInputStream(inputStream, decryptCipher);
      byte[] buf = new byte[1024];
      int bytesRead;
      while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        output.write(buf, 0, bytesRead);
      }
      cipherInputStream.close();
      return  new String(output.toByteArray());
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

The AESCipher utilizes the Java Cryptography Extension (JCE) API in order to security encrypt and decode an array of bytes to and from a String. The details of these API calls are out-of-scope for this book. Oracle has a good site to find out more information http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136007.html. I recommend the following book Beginning Java Security by David Hook.

We shall move on to binding filter and interceptors and how a developer can control which JAX-RS resources are matched to these types.

Binding filter and interceptors

As it stands, the AESCipherInterceptor class from previous section has a global binding. This means it will be invoked for all JAX-RS Resources in the application! We most likely do not want to encryption and decryption for all of the REST style resources in our application.

A filter or entity interceptor can be associated with a resource class or method by declaring a new binding annotation in the spirit of the Context and Dependency Injection (CDI). Annotations for association are declared with the JAX-RS meta-annotation @javax.ws.js.NameBinding.

We can create a custom annotation for denoting resources that need secure encryption. The following code is a new annotation called @Encrypt:

@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Encrypt { }

This is a runtime annotation and it can only be applied to class types or methods.

Now we can bind this annotation to the interceptor by applying to the class in the following way:

@Encrypt
@Provider
public class AESCipherInterceptor 
implements ReaderInterceptor, WriterInterceptor { 
  /* ... as before as */ }

To complete the puzzle, we only need to apply the custom annotation to methods in a REST style resource that we want to protect. Following is a particular class, called SensitiveResource, which demonstrates the principle:

@Path("/docs")
public class SensitiveResource {
  @Encrypt
  @GET
  @Path("{id}")
  @Produces("text/plain")
  public SensitiveDocument retrieve(@PathParam("id") String file )
  {
    /*...*/
  }

  @Encipher
  @POST
  @Path("{id}")
  @Consumes(MediaType.MULTIPART_FORM_DATA)
  public SensitiveDocument store(@PathParam("id") String file,
  @FormParam("file") InputStream inputStream )
  {
    /*...*/
  }
}

The method retrieve() in this REST style endpoint is annotated with @Encrypt. The JAX-RS provider will work out that this particular HTTP GET request on this resource is bound to the AESCipherInterceptor. The write interceptor will be invoked after the resource generates the response, which causes the response to be encrypting before the JAX runtime sends the result back to the client.

The resource method store() is triggered on HTTP POST request and also annotated with the @Encrypt method. This informs the runtime to bind an instance of the AESCipherInterceptor to the resource method. The read interceptor will be invoked first to decrypt the input stream before invoking the resource method, which results in the HTML Form encoded to be decrypted. Note that we must also annotate the resource method with @Consume tag, which stipulates the single HTML form parameter is a MIME multipart form upload.

Dynamic binding

There is still one other way to configure binding to a resource. Whereas the @NameBinding annotation means the configuration is a static means for a dynamic or runtime application of behavior that we cannot use this feature. Luckily, JAX-RS 2.0 provides an additional interface javax.ws.js.container.DynamicInterface.

The dynamic interface is designed for the registration of post-matching providers during a JAX-RS application initialization at the time of deployment. There is one single interface to implement called configure() and it takes two arguments, namely javax.ws.js.container.ResourceInfo and javax.ws.js.core.FeatureContext.

Let us write a new configuration feature class AESCipherDynamicFeature, which supports dynamic binding. Following is the new code snippet:

@Provider
public class AESCipherDynamicFeature 
implements DynamicFeature 
@Override
public void configure(ResourceInfo resourceInfo,FeatureContext config ) {
  if ( SensitiveResource.class.isAssignableFrom(resourceInfo.getResourceClass() &&resourceInfo.getResourceMethod()
    .isAnnotationPresent(GET.class)) {
    config.register( 
      new AESCipherInterceptor() );
    }
  }
}

The class must be annotated with the @Provider in order to be successfully scanned by the JAX-RS runtime. Once the runtime discovers the new feature and it can see that the class is a type of DynamicFeature then the runtime invokes configure() method. We verify the resource that we want to protect is the target, and we also check the REST style resource method is the correct one to apply this interceptor. Is this resource method the HTTP GET request method? When these conditions are true, then we use the FeatureContext instance to configure and associate an instance of the interceptor with the resource.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.35.247