JSF 2 Captcha使用

时间:2013-03-27 04:04:29

标签: jsf-2 java-ee-6 graphics2d servlet-3.0 prettyfaces

我的应用程序中存在一个问题,即我将Captcha组件构建为JSF自定义标记:

在我的JavaEE 6 webapp中我使用: JSF 2.1 + Jboss Richfaces 4.2.3 + EJB 3.1 + JPA 2.0 + PrettyFaces 3.3.3

我有一个JSF2自定义标记:

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

在我的XHTML页面中,名为accountEdit.xhtml,我显示了验证码:

                <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>

在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>

在我的web.xml中我已经配置了一个CaptchaServlet来处理在运行时生成验证码的请求:

<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>

我的CaptchaServlet实现:

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();
    }
}

当我在Glassfish 3.1.1上运行此应用程序时 在渲染时调用Servlet的doGet()方法

用于呈现的HttpServlet doGet()方法:

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

doGet()仅为Google Chrome呈现一次,因此可以正确呈现。

对于Firefox和IE,doGet()渲染两次更新Captcha Key但不更新页面上绘制的Captcha Image。

如果有人可能知道可以解决这个问题的原因以及为什么Chrome的这种行为与其他浏览器有所不同请允许我。

提前致谢!

2 个答案:

答案 0 :(得分:3)

浏览器正在缓存响应。您试图避免这种情况是不完整和不正确的:

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

请参考How to control web page caching, across all browsers?获取正确的设置:

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.

此外,为了使其更加健壮,请将具有当前时间戳的查询字符串以毫秒为单位添加到图像URL。这是一个示例,前提是您有一个java.util.Date实例作为托管bean,名称为now

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

(请注意,我还简化了获取请求上下文路径的方式,我只是不明白如果你通过../转到域根目录它是如何有用的) < / p>

答案 1 :(得分:0)

我找到了一个解决方案,这不是最佳解决方案,但它有效,在这里:

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方法:

    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();
    }

创建了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);
        }               
    }

}

点击“创建帐户”按钮时,Captcha重置:

CustomerMB.openCreateCustomerAccount():

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

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

也许这不是完美的解决方案,但至少它适用于所有浏览器。