Since the form I had to protect is presented by a Java EE 6 Web Module using Facelets, I decided that the best course of action was:
- Using a reCaptcha Java library, as suggested by the reCaptcha developer's documentation.
- Wrapping the library functionality into a custom JSF component, so that it could be easily reused in any Java EE web application.
- Writing a Facelets tag library descriptor so that I could use my component from both JSP and Facelets pages.
reCaptcha Java Library
The contributed reCaptcha Java library described in the reCaptcha developer's documentation is really easy to use but it does not offer the adequate tools that a Java EE developer needs. To summarize, it requires you to add Java scriptlets into a JSP page, something awful to see and awkward to maintain.With this code you can create your reCaptcha instance:
ReCaptcha c = ReCaptchaFactory.newReCaptcha("your_public_key", "your_private_key", false);
and with the createRecaptchaHtml method you can have it create the required HTML to show the reCaptcha widget:
c.createRecaptchaHtml(null, null);
Once the client submits the form where the widget is shown, the following piece of code can be used to determine whether the value introduced by the user is correct or not:
String remoteAddr = request.getRemoteAddr();
ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
reCaptcha.setPrivateKey("your_private_key");
String challenge = request.getParameter("recaptcha_challenge_field");
String uresponse = request.getParameter("recaptcha_response_field");
ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddr, challenge, uresponse);
reCaptchaResponse.isValid();
No doubt a custom tag would be much easier to use.
A reCaptcha Custom Tag
We would like to be able to write something like this in our pages, instead:<rc:recaptcha ... />
The required parameters that the library needs are the two reCaptcha keys, so our custom tag would end up like:
<rc:recaptcha id="..." publicKey="..." privateKey="..." />
The basics files you need to build a custom JSF components are:
- The component implementation file.
- The component's renderer.
- The component's tag handler.
The Component
Since users will input data through the component, our component class will extend the JSF UIInput class. Although we won't ever read what the user inputs, we use this class so that we can take advantage of the JSF infrastructure during the validation phase.Into our component skeleton, we create the setter methods so that the reCaptcha keys can be configured:
public class RecaptchaComponent extends UIInput {
static final String RecaptchaComponent_FAMILY = "RecaptchaComponentFamily";
private String publicKey;
private String privateKey;
@Override
public final String getFamily() {
return RecaptchaComponent_FAMILY;
}
public void setPublicKey(String s) {
publicKey = s;
}
public void setPrivateKey(String s) {
privateKey = s;
}
public String getPublicKey() {
if (publicKey != null)
return publicKey;
ValueExpression ve = this.getValueExpression("publicKey");
if (ve != null) {
return (String)ve.getValue(getFacesContext().getELContext());
} else {
return publicKey;
}
}
public String getPrivateKey() {
if (privateKey != null)
return privateKey;
ValueExpression ve = this.getValueExpression("privateKey");
if (ve != null) {
return (String)ve.getValue(getFacesContext().getELContext());
} else {
return privateKey;
}
}
}
As you may notice, since we want to accept EL expressions to set the reCaptcha keys, the corresponding getters take that into account and retrieve the value from a value expression if the user didn't use a literal.
The Renderer
The component renderer is pretty simple, since the HTML will be produced by the reCaptcha Java library:public class RecaptchaComponentRenderer extends Renderer {
static final String RENDERERTYPE = "RecaptchaComponentRenderer";
@Override
public void decode(FacesContext context,
UIComponent component) {
if (component instanceof UIInput) {
UIInput input = (UIInput) component;
String clientId = input.getClientId(context);
Map requestMap =
context.getExternalContext().getRequestParameterMap();
context.getExternalContext().getRequestParameterMap();
String newValue = (String) requestMap.get(clientId);
if (null != newValue) {
input.setSubmittedValue(newValue);
}
}
}
@Override
public void encodeBegin(FacesContext ctx,
UIComponent component) throws IOException {
}
@Override
public void encodeEnd(FacesContext ctx,
UIComponent component)
throws IOException {
if (component instanceof RecaptchaComponent) {
RecaptchaComponent rc = (RecaptchaComponent) component;
String publicKey = rc.getPublicKey();
String privateKey = rc.getPrivateKey();
if (publicKey == null || privateKey == null) {
throw new IllegalArgumentException("reCaptcha keys cannot be null. This is probably a component bug.");
}
ReCaptcha c = ReCaptchaFactory.newReCaptcha(publicKey, privateKey, false);
String createRecaptchaHtml = c.createRecaptchaHtml(null, null);
ResponseWriter writer = ctx.getResponseWriter();
writer.write(createRecaptchaHtml);
}
}
}
The Component Tag Handler
You can think of the tag handler as the intermediary between the custom tag you use in the page and the component class. It is basically responsible for getting and setting the component's properties.In this case, we will need the two setters to retrieve the reCaptcha keys and they must accept a ValueExpression object. When the tag handler sets the component's properties, it will check if the value are literals or value expressions and act accordingly:
- If they're literals, it will set them into the components fields.
- If they're value expressions, it will store them into the map of value expressions of the component for later evaluation (as seen in the component's code).
The code will be such as:
public class RecaptchaComponentTag extends UIComponentELTag {
private ValueExpression publicKey;
private ValueExpression privateKey;
public void setPublicKey(ValueExpression s) {
publicKey = s;
}
public void setPrivateKey(ValueExpression s) {
privateKey = s;
}
@Override
public String getComponentType() {
return RecaptchaComponent.RecaptchaComponent_FAMILY;
}
@Override
public String getRendererType() {
return RecaptchaComponentRenderer.RENDERERTYPE;
}
@Override
protected void setProperties(UIComponent component) {
super.setProperties(component);
if (component instanceof RecaptchaComponent) {
RecaptchaComponent c = (RecaptchaComponent) component;
if (publicKey != null) {
if (publicKey.isLiteralText()) {
c.setPublicKey(publicKey.getExpressionString());
} else {
c.setValueExpression("publicKey", publicKey);
}
}
if (privateKey != null) {
if (privateKey.isLiteralText()) {
c.setPrivateKey(privateKey.getExpressionString());
} else {
c.setValueExpression("privateKey", privateKey);
}
}
}
}
}
private ValueExpression publicKey;
private ValueExpression privateKey;
public void setPublicKey(ValueExpression s) {
publicKey = s;
}
public void setPrivateKey(ValueExpression s) {
privateKey = s;
}
@Override
public String getComponentType() {
return RecaptchaComponent.RecaptchaComponent_FAMILY;
}
@Override
public String getRendererType() {
return RecaptchaComponentRenderer.RENDERERTYPE;
}
@Override
protected void setProperties(UIComponent component) {
super.setProperties(component);
if (component instanceof RecaptchaComponent) {
RecaptchaComponent c = (RecaptchaComponent) component;
if (publicKey != null) {
if (publicKey.isLiteralText()) {
c.setPublicKey(publicKey.getExpressionString());
} else {
c.setValueExpression("publicKey", publicKey);
}
}
if (privateKey != null) {
if (privateKey.isLiteralText()) {
c.setPrivateKey(privateKey.getExpressionString());
} else {
c.setValueExpression("privateKey", privateKey);
}
}
}
}
}
The Validator
When the user submits the form, we need to check if the value he entered was correct or not. JSF input components undergo a validation phase during their life cycle, so that we can perform the reCaptcha validation during this phase.To do that, we need an instance of the Validator class and implement the validate method accordingly:
public class RecaptchaValidator implements Validator {
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
if (component instanceof RecaptchaComponent) {
RecaptchaComponent c = (RecaptchaComponent)component;
String remoteAddr = request.getRemoteAddr();
ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
reCaptcha.setPrivateKey(c.getPrivateKey());
String challenge =
request.getParameter("recaptcha_challenge_field");
String uresponse =
request.getParameter("recaptcha_response_field");
ReCaptchaResponse reCaptchaResponse =
reCaptcha.checkAnswer(remoteAddr, challenge, uresponse);
if (!reCaptchaResponse.isValid()) {
throw new ValidatorException(
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Invalid captcha", "Invalid captcha"));
}
}
}
}
As it can be seen, we just use the reCaptcha Java library code to perform the check.
Use the Validator Into the Component
To use the validator as a default validator, we can simply add it to the list of validator of our component during the component creation:public RecaptchaComponent() {
super();
addValidator(new RecaptchaValidator());
super();
addValidator(new RecaptchaValidator());
}
Also, we need to override the UIInput validate method so that our validator be called;
@Override
public void validate(FacesContext ctx) {
Validator[] validators = getValidators();
for (Validator v : validators) {
try {
v.validate(ctx, this, null);
} catch (ValidatorException ex) {
setValid(false);
FacesMessage message = ex.getFacesMessage();
if (message != null) {
message.setSeverity(FacesMessage.SEVERITY_ERROR);
ctx.addMessage(getClientId(ctx), message);
}
}
super.validate(ctx);
}
Validator[] validators = getValidators();
for (Validator v : validators) {
try {
v.validate(ctx, this, null);
} catch (ValidatorException ex) {
setValid(false);
FacesMessage message = ex.getFacesMessage();
if (message != null) {
message.setSeverity(FacesMessage.SEVERITY_ERROR);
ctx.addMessage(getClientId(ctx), message);
}
}
super.validate(ctx);
}
}
The JSF Configuration File
We now must tell JSF about this new component. Since we distribute it into a standalone JAR, we add a faces-config.xml file into the META-INF directory of the archive with this content:<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<component>
<component-type>RecaptchaComponentFamily</component-type>
<component-class>es.trafico.jsf.component.RecaptchaComponent</component-class>
</component>
<render-kit>
<renderer>
<description>Renderer.</description>
<component-family>RecaptchaComponentFamily</component-family>
<renderer-type>RecaptchaComponentRenderer</renderer-type>
<renderer-class>
es.trafico.jsf.component.RecaptchaComponentRenderer
</renderer-class>
</renderer>
</render-kit>
<validator>
<validator-id>recaptchaValidator</validator-id>
<validator-class>
es.trafico.jsf.component.RecaptchaValidator
</validator-class>
</validator>
</faces-config>
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<component>
<component-type>RecaptchaComponentFamily</component-type>
<component-class>es.trafico.jsf.component.RecaptchaComponent</component-class>
</component>
<render-kit>
<renderer>
<description>Renderer.</description>
<component-family>RecaptchaComponentFamily</component-family>
<renderer-type>RecaptchaComponentRenderer</renderer-type>
<renderer-class>
es.trafico.jsf.component.RecaptchaComponentRenderer
</renderer-class>
</renderer>
</render-kit>
<validator>
<validator-id>recaptchaValidator</validator-id>
<validator-class>
es.trafico.jsf.component.RecaptchaValidator
</validator-class>
</validator>
</faces-config>
The Tag Library Descriptor
To be able to use our new custom component in our JSP pages, we need a tag library descriptor. Since we're packaging our custom component in a standalone JAR file so that it can be used in all our applications, the tag library descriptor must be placed into the META-INF directory of the archive.Our taglib.tld file is like this:
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
<tlib-version>1.0</tlib-version>
<short-name>ReCaptcha-Library</short-name>
<uri>http://www.reacciona.es/rc</uri>
<tag>
<name>recaptcha</name>
<tag-class>
es.trafico.jsf.component.RecaptchaComponentTag
</tag-class>
<body-content>empty</body-content>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>publicKey</name>
<required>true</required>
<deferred-value>
<type>java.lang.String</type>
</deferred-value>
</attribute>
<attribute>
<name>privateKey</name>
<required>true</required>
<deferred-value>
<type>java.lang.String</type>
</deferred-value>
</attribute>
<short-name>ReCaptcha-Library</short-name>
<uri>http://www.reacciona.es/rc</uri>
<tag>
<name>recaptcha</name>
<tag-class>
es.trafico.jsf.component.RecaptchaComponentTag
</tag-class>
<body-content>empty</body-content>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>publicKey</name>
<required>true</required>
<deferred-value>
<type>java.lang.String</type>
</deferred-value>
</attribute>
<attribute>
<name>privateKey</name>
<required>true</required>
<deferred-value>
<type>java.lang.String</type>
</deferred-value>
</attribute>
</tag>
</taglib>
</taglib>
Using The Component
You can now use your new tag library in your JSP page adding this directory at the beginning of the page:<%@taglib prefix="rc" uri="http://www.reacciona.es/rc"%>
You can now add your custom component instances into your JSP pages (with JSF) this way:
<f:view>
...
<h:form>
<rc:recaptcha id="rc"
publicKey="#{yourBean.publicKey}"
privateKey="#{yourBean.privateKey}"/>
<h:message for="rc" />
</h:form>
</f:view>
In this example, we use a value expression to set both the reCaptcha keys using two properties of a managed bean of yours. This way, you won't hardcode any parameter into your pages.
Using the Custom Component With Facelets
You won't be able to use the custom component into a Facelets page until you write a Facelets tag library descriptor. You might be wondering why so many indirection layers: Facelets is designed to be more flexible than JSP and its tag descriptors reflect this design decision. Instead of having to describe your component down to the smallest detail, Facelets lets your just declare its existence and, for example, will set attributes at runtime, inspecting the component and tag classes.A basic Facelets tag library descriptor for the component is the following:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://www.reacciona.es/rc</namespace>
<tag>
<tag-name>recaptcha</tag-name>
<component>
<component-type>RecaptchaComponentFamily</component-type>
<renderer-type>RecaptchaComponentRenderer</renderer-type>
</component>
</tag>
</facelet-taglib>
<tag>
<tag-name>recaptcha</tag-name>
<component>
<component-type>RecaptchaComponentFamily</component-type>
<renderer-type>RecaptchaComponentRenderer</renderer-type>
</component>
</tag>
</facelet-taglib>
Please note that both the component and the renderer type are just the identifiers we declared in the JSF configuration file.
We can now use the custom component into a Facelets page adding the following namespace into the html tag at the beginning of the page:
xmlns:rc="http://www.reacciona.es/rc"
The syntax to add the component is the same syntax that we used in the JSP example.
No comments:
Post a Comment