At long last I have been able to set up a form signature for a Java/JSP application. The application I developed uses the Faces framework, and I set up a custom tag to handle the functionality. I am including the important Java classes here. The first Java class is the most important since it contains the functions that generate the signature components. I have documented the code in the javadoc comments.

FormSignatureValidator.java

JAVA:
  1. package com.threeleaf.jsf.components.formsignature;
  2.  
  3. import java.security.MessageDigest;
  4. import java.text.SimpleDateFormat;
  5. import java.util.Calendar;
  6. import java.util.Date;
  7.  
  8. import javax.faces.application.FacesMessage;
  9. import javax.faces.component.UIComponent;
  10. import javax.faces.context.FacesContext;
  11. import javax.faces.validator.Validator;
  12. import javax.faces.validator.ValidatorException;
  13. import javax.servlet.ServletContext;
  14.  
  15. import org.apache.log4j.Logger;
  16.  
  17. import com.threeleaf.agentguidedselling.model.StringEncrypter;
  18. import com.threeleaf.agentguidedselling.model.StringEncrypter.EncryptionException;
  19.  
  20. /**
  21. * Add a "random" but verifiable form field in Faces to prevent many kinds of
  22. * scripted spam attacks. The idea is to tie a browser session to a web address
  23. * during a given timeframe. The goal is to make sure that 1) no generated
  24. * script will be valid for more than an hour 2) robots to different machines
  25. * cannot be spawned. The technique can be worked around, but the hope is that
  26. * spammers will not find it worth their while to do so. If stronger prevention
  27. * is needed, then captchas can be used.
  28. *
  29. * @author John A. Marsh
  30. */
  31. public class FormSignatureValidator implements Validator {
  32.     private Logger logger = Logger.getLogger(this.getClass().getName());
  33.  
  34.     /*
  35.      * Generates the variable to be used in the form signature input tag. The
  36.      * value changes every hour. It is assumed
  37.      * that no single form will ever take an hour to fill out and submit. Every
  38.      * hour, the length of the random variable will change. The object is to
  39.      * twart script robots. It is not perfect, but it works well without
  40.      * requiring captchas.
  41.      *
  42.      * @param optional calendar object with which to calculate the field.
  43.      * Overloaded to default to the current time.
  44.      *
  45.      * @return a reproducable "random" string to be used as a form field name.
  46.      *
  47.      * @author John A. Marsh
  48.      * 
  49.      */
  50.     public static String formSignatureID(Calendar now) {
  51.         // The filed name will change once an hour
  52.         SimpleDateFormat formatter = new SimpleDateFormat("hhdDEEEEFMMMMyyyyzzzz");
  53.         String fieldName = formatter.format(now.getTime());
  54.         // We want to have the string be between 7 and 14 characters in a
  55.         // reproducably "random" manner
  56.         int stringLength = 7 + fieldName.length() % 7;
  57.         fieldName = md5(fieldName);
  58.         /*
  59.          * We want the first character to begin with a letter, not a number, to
  60.          * guarantee browser and language compatibilty We want to start as close
  61.          * to the beginning as possible. It is highly improbably that one of the
  62.          * hex letters would not be found closer to the beginning than the first
  63.          * calculation, but it is there just in case.
  64.          */
  65.         int firstCharacter = 31 - stringLength;
  66.         if (fieldName.indexOf("a")> -1)
  67.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("a"));
  68.         if (fieldName.indexOf("b")> -1)
  69.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("b"));
  70.         if (fieldName.indexOf("c")> -1)
  71.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("c"));
  72.         if (fieldName.indexOf("d")> -1)
  73.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("d"));
  74.         if (fieldName.indexOf("e")> -1)
  75.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("e"));
  76.         if (fieldName.indexOf("f")> -1)
  77.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("f"));
  78.         fieldName = fieldName.substring(firstCharacter, firstCharacter + stringLength);
  79.         return fieldName;
  80.     }
  81.  
  82.     public static String formSignatureID() {
  83.         Calendar now = Calendar.getInstance();
  84.         return formSignatureID(now);
  85.     }
  86.  
  87.     /*
  88.      * Create a form signature. This is basically a timestamp. The time in
  89.      * milliseconds is hashed and then paired with the encrypted value. On the
  90.      * other side, the time is unencrypted and compared with the hashed value.
  91.      *
  92.      * @param seed Optional "password" for the form
  93.      *
  94.      * @return the generated key
  95.      *
  96.      * @trows EncryptionException
  97.      *
  98.      * @author John A. Marsh
  99.      * 
  100.      */
  101.     public static String formSignatureGenerate(String seed) throws EncryptionException {
  102.         Logger logger = Logger.getLogger("FormSignatureValidator.formSignatureGenerate(\"" + seed + "\")");
  103.         Date currentDate = new Date();
  104.         String millitime = Long.toString(currentDate.getTime());
  105.         StringEncrypter encrypter = new StringEncrypter("DES", seed);
  106.         if(logger.isDebugEnabled()) {
  107.             logger.debug("Time in milliseconds: " + millitime);
  108.             logger.debug("md5(millitime): " + md5(millitime));
  109.             logger.debug("encrypter.encrypt(millitime): " + encrypter.encrypt(millitime));
  110.         }
  111.         return md5(millitime) + encrypter.encrypt(millitime);
  112.     }
  113.  
  114.     public static String formSignatureGenerate() {
  115.         ServletContext context = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
  116.         Logger logger = Logger.getLogger("FormSignatureValidator.formSignatureGenerate()");
  117.         String seed = context.getServerInfo();
  118.         logger.debug("Generated seed: " + seed);
  119.         try {
  120.             return formSignatureGenerate(seed);
  121.         } catch (EncryptionException e) {
  122.             return "error";
  123.         }
  124.     }
  125.  
  126.     public static String md5(String string) {
  127.         try {
  128.             MessageDigest md = MessageDigest.getInstance("MD5");
  129.             md.update(string.getBytes());
  130.             byte[] digest = md.digest();
  131.             String hexString = hexify(digest);
  132.             return hexString;
  133.         } catch (Exception e) {
  134.             e.printStackTrace();
  135.             return null;
  136.         }
  137.     }
  138.  
  139.     static private String[] HEX = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
  140.  
  141.     static public String hexify(byte byValue) {
  142.         int nValue = (int) byValue + 128;
  143.         return HEX[nValue / 16] + HEX[nValue % 16];
  144.     }
  145.  
  146.     static public String hexify(byte[] bytes) {
  147.         String hexed = "";
  148.         for (int i = 0; i <bytes.length; i++) {
  149.             hexed += hexify(bytes[i]);
  150.         }
  151.         return hexed;
  152.     }
  153.    
  154. }

The following code is used to render the custom tag in Faces.

FormSignatureRenderer.java

JAVA:
  1. package com.threeleaf.jsf.components.formsignature;
  2.  
  3. import java.io.IOException;
  4. import java.util.Map;
  5.  
  6. import javax.faces.component.UIComponent;
  7. import javax.faces.component.UIInput;
  8. import javax.faces.context.FacesContext;
  9. import javax.faces.context.ResponseWriter;
  10. import javax.faces.render.Renderer;
  11.  
  12. /**
  13. * Dictates how the HTML element is to be rendered.
  14. * The form signature is a hidden field in a form. Its name and
  15. * value are auto generated using the formulas in FormSignatureValidator.
  16. *
  17. * @author      John A. Marsh
  18. * @see         FormSignatureValidator
  19. *
  20. */
  21. public class FormSignatureRenderer extends Renderer {
  22.  
  23.     public FormSignatureRenderer() {
  24.         super();
  25.     }
  26.  
  27.     /* (non-Javadoc)
  28.      *
  29.      * This method is responsible for taking any parameters that were passed in from a form post and setting the value on the component.
  30.      *
  31.      * @see javax.faces.render.Renderer#decode(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
  32.      */
  33.     public void decode(FacesContext context, UIComponent component) {
  34.        
  35.         assertValidInput(context, component);
  36.        
  37.         if (component instanceof UIInput) {
  38.             UIInput input = (UIInput) component;
  39.             String clientId = input.getClientId(context);
  40.             Map requestMap = context.getExternalContext().getRequestParameterMap();
  41.             String newValue = (String) requestMap.get(clientId);
  42.             if (null != newValue) {
  43.                 input.setSubmittedValue(newValue);
  44.             }
  45.         }
  46.     }
  47.  
  48.     /* (non-Javadoc)
  49.      * @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
  50.      */
  51.     public void encodeEnd(FacesContext context, UIComponent component)
  52.             throws IOException {
  53.         assertValidInput(context, component);
  54.         String formSignature = "";
  55.        
  56.         ResponseWriter writer = context.getResponseWriter();
  57.         writer.startElement("input", component);
  58.         writer.writeAttribute("type", "hidden", null);
  59.         String id = FormSignatureValidator.formSignatureID();
  60.         writer.writeAttribute("id", component.getParent().getId() + ":" + id, "id");
  61.         writer.writeAttribute("name", id, "id");
  62.         formSignature = FormSignatureValidator.formSignatureGenerate();
  63.         writer.writeAttribute("value", formSignature, "value");
  64.         writer.endElement("input");
  65.     }
  66.  
  67.     /**
  68.      * Verify that the context and the component are not null.
  69.      * Every Faces Renderer component should do this.
  70.      *
  71.      * @author  John A. Marsh
  72.      * @param   context
  73.      * @param   component
  74.      *
  75.      */
  76.     private void assertValidInput(FacesContext context, UIComponent component) {
  77.         if (context == null) {
  78.             throw new NullPointerException("context should not be null");
  79.         } else if (component == null) {
  80.             throw new NullPointerException("component should not be null");
  81.         }
  82.     }
  83. }

FormSignatureTag.java

JAVA:
  1. package com.threeleaf.jsf.components.formsignature;
  2.  
  3. import javax.faces.component.UIComponent;
  4. import javax.faces.context.FacesContext;
  5. import javax.faces.webapp.UIComponentTag;
  6.  
  7. /**
  8. * Code to create the form signature tag
  9. *
  10. * @author      John A. Marsh
  11. *
  12. */
  13. public class FormSignatureTag extends UIComponentTag {
  14.  
  15.     private static final String FORM_SIGNATURE_COMPONENT_TYPE = "FORM_SIGNATURE_INPUT";
  16.     private static final String FORM_SIGNATURE_RENDER_TYPE = "FORM_SIGNATURE_RENDERER";
  17.  
  18.     /* (non-Javadoc)
  19.      * @see javax.faces.webapp.UIComponentTag#getComponentType()
  20.      */
  21.     public String getComponentType() {
  22.         return FORM_SIGNATURE_COMPONENT_TYPE;
  23.     }
  24.     /* (non-Javadoc)
  25.      * @see javax.faces.webapp.UIComponentTag#getRendererType()
  26.      */
  27.     public String getRendererType() {
  28.         // TODO Auto-generated method stub
  29.         return FORM_SIGNATURE_RENDER_TYPE;
  30.     }
  31.    
  32.     protected void setProperties(UIComponent component) {
  33.         FacesContext context = FacesContext.getCurrentInstance();
  34.         super.setProperties(component);
  35.         /*if (null != value) {
  36.             if (isValueReference(value)) {
  37.                 ValueBinding vb = context.getApplication().createValueBinding(
  38.                         value);
  39.                 component.setValueBinding("value", vb);
  40.             } else {
  41.                 ((UIInput) component).setValue(value);
  42.             }
  43.         }*/
  44.     }
  45. }

UIFormSignature.java

JAVA:
  1. package com.threeleaf.jsf.components.formsignature;
  2.  
  3. import javax.faces.component.UIInput;
  4.  
  5. /**
  6. * Sets up the user interface compontent for the form signature.
  7. *
  8. * @author John A. Marsh
  9. */
  10. public class UIFormSignature extends UIInput {
  11.     public static final String FORM_SIGNATURE_FAMILY = "FSFAMILY";
  12.  
  13.     /**
  14.      * Adding the validator directly to the component, so it does not need to be specified separately.
  15.      */
  16.     public UIFormSignature() {
  17.         super();
  18.         addValidator(new FormSignatureValidator());
  19.     }
  20.    
  21.     public String getFamily() {
  22.         return FORM_SIGNATURE_FAMILY;
  23.     }
  24. }

Leave a Reply

You must be logged in to post a comment.