In registration form, used in EmForge we needed Captcha-implementation to save it from robots. I looked into internet -but did not found any ready solution for JSF-based project - so, here is one. Captcha Generation For generating Captcha we used JCaptcha library. It is quite powerfull library with many features - and one of them - generation and validation of captcha images. Adding JCaptcha into Project. Since both: EmForge and JCaptcha are Maven based projects and I found jcaptcha in the public maven repositories it was quite easy to add jcaptcha into project - just add it into projects dependency in pom.xml
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha-all</artifactId>
<version>1.0-RC6</version>
</dependency>
If your project is not maven-based - just download jcaptcha jar and add into your WEB-INF/libs folder. Including Captcha into JSF Page For rendering Captcha in JSF page we used paint2D control from rich-faces:
<h:form id="registerForm">
<h:panelGrid columns="2" styleClass="properties-table">
...
<!-- CaptchaImage -->
<rich:paint2D id="captcha"
width="#{registerController.captchaWidth}"
height="#{registerController.captchaHeight}"
format="jpeg"
paint="#{registerController.paintCaptcha}"
data="#{registerController.randomString}"/>
<h:outputText value="Text on Image:"/>
<h:panelGroup>
<h:inputText id="secureText"
value="#{registerController.secureText}"
size="40"
required="true">
<emforge:captchaValidator/>
</h:inputText>
<h:message for="secureText" errorClass="errorMessage"/>
</h:panelGroup>
<h:commandLink action="#{registerController.submit}">
<h:outputText value="Submit"/>
</h:commandLink>
</h:panelGrid>
</h:form>
In RegisterController we need to: - define id, used for storing generated text;
- Add ImageCaptchaService;
- Add Method, generated image
- And accessor to secure text, entered by user
- And method to generate random string:
(only part of class is here - whole code you can see here: RegisterController.java)
public class RegisterController extends BaseControllerImpl implements InitializingBean {
public static final String CAPTCHA_ID = "EmForge";
private ImageCaptchaService m_imageCaptchaService;
private String m_secureText;
public void setImageCaptchaService(ImageCaptchaService i_imageCaptchaService) {
m_imageCaptchaService = i_imageCaptchaService;
}
public String getSecureText() {
return m_secureText;
}
public void setSecureText(String i_secureText) {
m_secureText = i_secureText;
}
protected BufferedImage generateCaptcha() {
try {
return m_imageCaptchaService.getImageChallengeForID(CAPTCHA_ID);
} catch (Exception ex) {
logger.error("Cannot generate captcha image", ex);
return null;
}
}
public int getCaptchaWidth() {
return 200;
}
public int getCaptchaHeight() {
return 100;
}
/** Paints Captcha Image
*
* @param g2d
* @param obj
*/
public void paintCaptcha(Graphics2D g2d, Object obj) {
BufferedImage secureImage = generateCaptcha();
try {
g2d.setClip(0,0,secureImage.getWidth(), secureImage.getHeight());
g2d.drawImage(secureImage, 0,0, null);
} catch (Exception ex) {
logger.error("Cannot generate captcha image", ex);
}
}
/** Generates Random Text for displaying on the image */
public String getRandomString() {
String str=new String("QAa0bcLdUK2eHfJgTP8XhiFj61DOklNm9nBoI5pGqYVrs3CtSuMZvwWx4yE7zR");
StringBuffer sb=new StringBuffer();
Random r = new Random();
int te=0;
for(int i=1;i<=6;i++){
te=r.nextInt(62);
sb.append(str.charAt(te));
}
return sb.toString();
}
Since JCaptcha supports integration with spring-framework, ImageCaptchaService may be defined as spring bean and passed int registerController as param (part of beans configuration):
<beans>
...
<bean id="imageCaptchaService" class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService" scope="session"/>
<bean id="registerController" class="ru.emdev.EmForge.security.RegisterController" scope="request">
...
<property name="imageCaptchaService" ref="imageCaptchaService"/>
</bean>
...
</beans>
Now, you will able to generate and render the captcha image, as well as enter the text. Validating Captcha For validating Captcha we implemented special validator - CaptchaValidator (assigned to secureText input):
package ru.emdev.EmForge.security.web.validator;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import ru.emdev.EmForge.security.RegisterController;
import com.octo.captcha.service.image.ImageCaptchaService;
/**
* Validates inputed name for a project
*
* @author mchirkov
*/
public class CaptchaValidator implements Validator {
public static final char[] INVALID_CHARS = new char[] {'*', '@', '/', '\\'};
public static final int MAX_NAME_LENGTH = 50;
public static final String VALUEREQUIRED_ERROR_MESSAGE = "Value is required.";
public static final String TEXTNOTNATCH_ERROR_MESSAGE = "Text not match text on image";
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException {
String val = StringUtils.strip((String)value);
if (StringUtils.isEmpty(val)) {
throw new ValidatorException(
new FacesMessage(VALUEREQUIRED_ERROR_MESSAGE, VALUEREQUIRED_ERROR_MESSAGE)
);
}
HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getRequest();
WebApplicationContext appContext =
WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
assert appContext != null;
ImageCaptchaService imageCaptchaService = (ImageCaptchaService)appContext.getBean("imageCaptchaService");
if (!imageCaptchaService.validateResponseForID(RegisterController.CAPTCHA_ID, val)) {
throw new ValidatorException(
new FacesMessage(TEXTNOTNATCH_ERROR_MESSAGE, TEXTNOTNATCH_ERROR_MESSAGE));
}
}
}
ImageCaptchaService, stored inside itself the text, generated in image, so, here, we simple get this bean from Spring Context, and validate it So, now we only need to declare validator in faces-config.xml:
<validator>
<validator-id>captchaValidator</validator-id>
<validator-class>ru.emdev.EmForge.security.web.validator.CaptchaValidator</validator-class>
</validator>
and define validator tag in our facelets taglib (emforge.taglib.xml)
<tag>
<tag-name>captchaValidator</tag-name>
<validator>
<validator-id>captchaValidator</validator-id>
</validator>
</tag>
What is all. Enjoy! |