Search code examples
javajspstruts2captchasimplecaptcha

Displaying SimpleCaptcha in Struts 2 Form


I am trying to implement SimpleCaptcha with Struts 2, so far the image is displaying. However, it is displaying at the top of the <s:form>.

enter image description here

register.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="s" uri="/struts-tags"%>

<head>
...
<s:head />
</head>

<body>
  <b>REGISTER</b>
  <p>Please fill up the form below.</p>
  <s:form action="register" method="post">
    <s:textfield label="Enter username" key="userId" maxlength="25"
      size="30" />
    <s:textfield label="Enter email" key="userEmail1" type="email"
      placeholder="someone@domain.com" size="30" />
    <s:textfield label="Re-enter email" key="userEmail2" type="email"
      placeholder="someone@domain.com" size="30" />
    <s:password label="Enter password" key="userPassword1" size="30" />
    <s:password label="Re-enter password" key="userPassword2"
      size="30" />
    <img src="<c:url value='simple-captcha.png' />" />
    <br />
    Cannot read? Refresh page for new CAPTCHA.                      
    <s:textfield label="Enter CAPTCHA" key="captchaAnswer" size="30" />
    <s:submit value="Register" />
  </s:form>
</body>

How do I make the image appear above the Enter CAPTCHA textfield as specified in the code?


Solution

  • The image should be generated by the captcha action. Then you provide the URL to this action in <img> tag. To implement captcha in your project follow steps below

    1. Add the jar to your web project classpath: simplecaptcha-1.2.1.jar. Typically inside web-inf/lib folder.

    2. Add new action class RegisterAction

    Note: The following code is using convention plugin to map actions and for simplicity DMI is used to invoke some methods of the action class when form is submitted. To turn on DMI use a constant in struts.xml:

    <constant name="struts.enable.DynamicMethodInvocation" value="true"/>
    

    RegisterAction.java:

    public class RegisterAction extends ActionSupport {
      private String userId;
      private String userEmail1;
      private String userEmail2;
      private String userPassword1;
      private String userPassword2;
    
      private String captchaResponse;
      private InputStream inputStream;
    
      //getters and setters
    
      public String create() {
        //RegisterAction is the form bean of the current action and captchaResponse is the field of user input
    
        String answer = (String) ActionContext.getContext().getSession().get("CorrectAnswer");
        if (answer == null || getCaptchaResponse()==null || !answer.equals(getCaptchaResponse())){
          addFieldError("captchaResponse", getText("error.captcha"));
        }
        return SUCCESS;
      }
    
      @Action(value = "captcha", results = {@Result(type="stream", params = {"contentType", "image/jpeg"})})
      public String captcha() {
        try {
          Captcha captcha = new Captcha.Builder(200, 50).addText(new DefaultTextProducer()).gimp(new DropShadowGimpyRenderer()).build();
          ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
          //write the image
          CaptchaServletUtil.writeImage(outputStream, captcha.getImage());
          //store the answer for this in session
          ActionContext.getContext().getSession().put("CorrectAnswer", captcha.getAnswer());
          //return image
          inputStream = new ByteArrayInputStream(outputStream.toByteArray());
          return SUCCESS;
        } catch (Exception e) {
          e.printStackTrace();
          throw e;
        }
    
      }
    
    }
    

    RegisterAction.properties contains the following value for the error key:

    RegisterAction.properties:

    error.captcha=Invalid value of shown text!
    

    so we check either pass successfully or add to errors error regarding captcha.

    1. Add register.jsp Typically in web-inf\content

    register.jsp:

    <!DOCTYPE html>
    <%@ taglib prefix="s" uri="/struts-tags"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>Register</title>
    </head>
    <body>
    <b>REGISTER</b>
    <p>Please fill up the form below.</p>
    <s:form action="register" method="post">
      <s:textfield label="Enter username" key="userId" maxlength="25"
                   size="30" />
      <s:textfield label="Enter email" key="userEmail1" type="email"
                   placeholder="someone@domain.com" size="30" />
      <s:textfield label="Re-enter email" key="userEmail2" type="email"
                   placeholder="someone@domain.com" size="30" />
      <s:password label="Enter password" key="userPassword1" size="30" />
      <s:password label="Re-enter password" key="userPassword2"
                  size="30" />
      <tr><td>
      <img id="captchaImg" src="<s:url action='captcha'/>" alt="Captcha Image" height="45">
      <img src="<c:url value='/images/reload.jpg' />" alt="Reload" onclick="document.forms[0].captchaImg.src='<s:url action='captcha'/>'+'?id='+Math.random();" style="cursor:pointer"/>
      <s:textfield label="Enter CAPTCHA" key="captchaResponse" size="30" requiredLabel="*"/>
      <tr><td>
      Cannot read? Refresh page for new CAPTCHA.
      </td></tr>
      <s:submit method="create" value="Register" />
    </s:form>
    
    </body>
    </html>
    

    This will construct the Captcha and the text field to enter the value and Struts error message to show errors in captchaResponse field, plus the refresh icon.

    NOTE: a good trick we used here is the javascript Math.random() function, this way prevent certain browsers like Firefox to cache the URL and keep posting the same Captcha image, this enforce it to get the new value without doing any more effort.

    Here is how it will looks like: enter image description here

    For more details refer to the web site : SimpleCaptcha