Displaying SimpleCaptcha in Struts 2 Form

5k views Asked by At

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="[email protected]" size="30" />
    <s:textfield label="Re-enter email" key="userEmail2" type="email"
      placeholder="[email protected]" 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?

2

There are 2 answers

1
Roman C On BEST ANSWER

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="[email protected]" size="30" />
  <s:textfield label="Re-enter email" key="userEmail2" type="email"
               placeholder="[email protected]" 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

0
k_rollo On

This is just to show how I went about the solution. I was unaware you can put <tr><td> inside the <s:form>. Thanks to Roman C, I got the CAPTCHA image to display where I wanted it to.

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"
            placeholder="someone" size="30" />
        <s:textfield label="Enter email" key="userEmail1"
            placeholder="[email protected]" size="30" />
        <s:textfield label="Confirm email" key="userEmail2"
            placeholder="[email protected]" size="30" />
        <s:password label="Enter password" key="userPassword1" size="30" />
        <s:password label="Confirm password" key="userPassword2"
            size="30" />
        <tr>
            <td></td>
            <td>
                <img src="<c:url value='simple-captcha.png' />" />
                <br />
                Cannot read? Press F5 to refresh.
            </td>
        </tr>
        <s:textfield label="Enter code" key="captchaAnswer" size="30" />
        <s:submit value="Register" />
    </s:form>
</body>

RegisterAction.java:

import nl.captcha.Captcha;
...

public class RegisterAction extends ActionSupport implements SessionAware, Message {
    private static final long serialVersionUID = 1L;

    private Map<String, Object> session;

    private String userId;
    private String userEmail1;
    private String userEmail2;
    private String userPassword1;
    private String userPassword2;
    private String captchaAnswer;

    @Override
    public String execute() throws Exception {      
        // business logic to insert user into database

        return SUCCESS;
    }

    @Override
    public void validate() {
        Captcha captcha = (Captcha) session.get(Captcha.NAME);

        if(!captcha.isCorrect(getCaptchaAnswer())) {
            addFieldError("captchaAnswer", INCORRECT_CAPTCHA);
        }

        // other validations
    }

    @Override
    public void setSession(Map<String, Object> session) {
        this.session = session;
    }

    // getters and setters
}

struts.xml:

<struts>
    <constant name="struts.devMode" value="true" />
    <package name="default" extends="struts-default">
        <action name="register" class="com.mypackage.action.RegisterAction">
            <result name="success">/login.jsp</result>
            <result name="input">/register.jsp</result>
        </action>

        <!-- other actions -->
    </package>
</struts>

web.xml:
(The <init-param> is optional.)

<servlet>
    <servlet-name>SimpleCaptcha</servlet-name>
    <servlet-class>nl.captcha.servlet.SimpleCaptchaServlet</servlet-class>
    <init-param>
        <param-name>captcha-width</param-name>
        <param-value>200</param-value>
    </init-param>
    <init-param>
        <param-name>captcha-height</param-name>
        <param-value>50</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>SimpleCaptcha</servlet-name>
    <url-pattern>/simple-captcha.png</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

OUTPUT:

enter image description here