我的应用程序中存在一个问题,即我将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的这种行为与其他浏览器有所不同请允许我。
提前致谢!
答案 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;
}
也许这不是完美的解决方案,但至少它适用于所有浏览器。