Question

I have an issue in my application using where I have a Captcha component built as a JSF Custom Tag:

in my JavaEE 6 webapp I use: JSF 2.1 + Jboss Richfaces 4.2.3 + EJB 3.1 + JPA 2.0 + PrettyFaces 3.3.3

I have a JSF2 custom tag that is:

<tag>
    <tag-name>captcha</tag-name>
    <source>tags/captcha.xhtml</source>
</tag>  

in my XHTML page called accountEdit.xhtml I have the captcha being displayed:

                <ui:fragment rendered="#{customerMB.screenComponent.pageName eq 'create'}">
                    <div class="form_row">
                            <label class="contact"><strong>#{msg.captcha}:</strong>
                            </label>
                            <atl:captcha></atl:captcha>                     
                    </div>                                                          
                </ui:fragment>

in captcha.xhtml:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:a4j="http://richfaces.org/a4j"
    xmlns:rich="http://richfaces.org/rich">

    <table border="0">
        <tr>
            <td>
            <h:graphicImage id="capImg" value="#{facesContext.externalContext.requestContextPath}/../captcha.jpg" />
            </td>
            <td><a4j:commandButton id="resetCaptcha" value="#{msg.changeImage}" immediate="true" action="#{userMB.resetCaptcha}" >
                <a4j:ajax render="capImg" execute="@this" />                
            </a4j:commandButton></td>
        </tr>
        <tr>
            <td><h:inputText value="#{userMB.captchaComponent.captchaInputText}" /></td>            
        </tr>
    </table>

</ui:composition>

in my web.xml I have configured a CaptchaServlet that handles the request for generating a captcha during runtime:

<servlet>   
    <servlet-name>CaptchaServlet</servlet-name>
    <servlet-class>com.myapp.web.common.servlet.CaptchaServlet</servlet-class>      
    <init-param>
        <description>passing height</description>
        <param-name>height</param-name>
        <param-value>30</param-value>
    </init-param>
    <init-param>
        <description>passing width</description>
        <param-name>width</param-name>
        <param-value>120</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
</servlet>


<servlet-mapping>
    <servlet-name>CaptchaServlet</servlet-name>
    <url-pattern>/captcha.jpg</url-pattern>
</servlet-mapping>

My CaptchaServlet implementation:

public class CaptchaServlet extends HttpServlet {

    /**
     * 
     */
    private static final long serialVersionUID = 6105436133454099605L;

    private int height = 0;
    private int width = 0;
    public static final String CAPTCHA_KEY = "captcha_key_name";

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        height = Integer
                .parseInt(getServletConfig().getInitParameter("height"));
        width = Integer.parseInt(getServletConfig().getInitParameter("width"));
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse response)
            throws IOException, ServletException {

        // Expire response
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Max-Age", 0);

        BufferedImage image = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics2D = image.createGraphics();
        Hashtable<TextAttribute, Object> map = new Hashtable<TextAttribute, Object>();
        Random r = new Random();
        String token = Long.toString(Math.abs(r.nextLong()), 36);
        String ch = token.substring(0, 6);
        Color c = new Color(0.6662f, 0.4569f, 0.3232f);
        GradientPaint gp = new GradientPaint(30, 30, c, 15, 25, Color.white,
                true);
        graphics2D.setPaint(gp);
        Font font = new Font("Verdana", Font.CENTER_BASELINE, 26);
        graphics2D.setFont(font);
        graphics2D.drawString(ch, 2, 20);
        graphics2D.dispose();
        HttpSession session = req.getSession(true);
        session.setAttribute(CAPTCHA_KEY, ch);

        OutputStream outputStream = response.getOutputStream();
        ImageIO.write(image, "jpeg", outputStream);
        outputStream.close();
    }
}

When I run this app on Glassfish 3.1.1 when the Servlet's doGet() method is called while rendering

for the HttpServlet doGet() method that renders:

<h:graphicImage id="capImg" value="#{facesContext.externalContext.requestContextPath}/../captcha.jpg" />

doGet() renders only once for Google Chrome, thus rendering correctly.

For Firefox and IE doGet() renders twice updating the Captcha Key but not updating the painted Captcha Image on the page.

If anyone might know what could be a fix for this and why it has this behavior for Chrome different from other browsers please let me.

Thanks in advance!

Was it helpful?

Solution 2

I found a solution for this, is not the optimal solution but it works, here it goes:

captcha.xhtml

<table border="0">
    <tr>
        <td>
            <h:graphicImage url="#{request.contextPath}/../jcaptcha"/>
        </td>
        <td>
            <input type='text' name='j_captcha_response' value='' />
        </td>
    </tr>
</table>

CaptchaServlet doGet method:

    protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {

        byte[] captchaChallengeAsJpeg = null;
        // the output stream to render the captcha image as jpeg into
        ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
        try {
        // get the session id that will identify the generated captcha.
        //the same id must be used to validate the response, the session id is a good candidate!
        String captchaId = httpServletRequest.getSession().getId();
            // call the ImageCaptchaService getChallenge method
            BufferedImage challenge =
                    CaptchaServiceSingleton.getImageChallengeForID(captchaId,
                            httpServletRequest.getLocale());
            // a jpeg encoder
            JPEGImageEncoder jpegEncoder =
                    JPEGCodec.createJPEGEncoder(jpegOutputStream);
            jpegEncoder.encode(challenge);
        } catch (IllegalArgumentException e) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        } catch (CaptchaServiceException e) {
            httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }
        captchaChallengeAsJpeg = jpegOutputStream.toByteArray();

        // flush it in the response
        httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
        httpServletResponse.setHeader("Pragma", "no-cache");
        httpServletResponse.setDateHeader("Expires", 0);
        httpServletResponse.setContentType("image/jpeg");
        ServletOutputStream responseOutputStream =
                httpServletResponse.getOutputStream();
        responseOutputStream.write(captchaChallengeAsJpeg);
        responseOutputStream.flush();
        responseOutputStream.close();
    }

created CaptchaServiceRequestSingleton.java

    package com.myapp.web.common.listener;

    import java.awt.image.BufferedImage;
    import java.util.HashMap;
    import java.util.Locale;

    import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;
    import com.octo.captcha.service.image.ImageCaptchaService;

public class CaptchaServiceSingleton {

    private static ImageCaptchaService instance = new DefaultManageableImageCaptchaService();
    private static final int MAX_CACHE_SIZE = 200;
    private static HashMap<String, BufferedImage> captchaImgCache = new HashMap<String, BufferedImage>();

    public static ImageCaptchaService getInstance(){
        return instance;
    }

    public static BufferedImage getImageChallengeForID(String id, Locale locale) {
        if (captchaImgCache.containsKey(id)) {
            return captchaImgCache.get(id);
        } else {
            BufferedImage bImage = instance.getImageChallengeForID(id, locale);

            // if limit reached reset captcha cache
            if (captchaImgCache.size() > MAX_CACHE_SIZE) {
                captchaImgCache = new HashMap<String, BufferedImage>();
            }

            captchaImgCache.put(id, bImage);
            return bImage;
        }
    }

    public static void resetImageChallengeForID(String id) {        
        if (captchaImgCache.containsKey(id)) {      
            captchaImgCache.remove(id);
        }               
    }

}

when clicking on "Create Account" button Captcha is reset:

CustomerMB.openCreateCustomerAccount():

public String openCreateCustomerAccount() {
    customerAccountEditVO = new CustomerAccountVO();
    screenComponent.setPageName(NameConstants.CREATE);
    getUserMB().resetCaptcha();
    return null;
}

in UserMB.resetCaptcha():

public String resetCaptcha() {
    CaptchaServiceSingleton.resetImageChallengeForID(JSFUtil.getRequest().getRequestedSessionId());     
    return null;
}

Perhaps it's not the perfect solution but at least it's working for all Browsers.

OTHER TIPS

The browser is caching the response. Your attempt to avoid this is incomplete and incorrect:

response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Max-Age", 0);

Please refer How to control web page caching, across all browsers? for the proper set:

response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.

Further, to make it more robust, add a query string with the current timestamp in millis to the image URL. Here's an example provided that you've a java.util.Date instance as managed bean with the name now:

<h:graphicImage id="capImg" value="#{request.contextPath}/../captcha.jpg?#{now.time}" />

(please note that I also simplified the way to get the request context path, I only don't understand how it's useful if you go to domain root by ../ anyway)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top