使用jcaptcha在Struts中生成验证码

燕野
2023-12-01
jcaptcha, 是一个Java开源项目, 为Java Completely Automated Public Test to tell Computers and Humans Apart 的简写, 字面意思就是Java实现的可以区分计算机和人的测试.
具体的测试方法有很多种, 比如:
验证码 - (这里讨论的)
音频 - (比如朗读几个字母, 用户输入这些字母来验证)

JCAPTCHA在Struts中配置很简单, 主要是以下三个部分:

1. 需要一个Action或者Servlet来作为页面的接口. 在访问该Acton或Servlet的时候, 调用(2) 生成随机的验证对象, 和当前的Session关联起来;
2. 一个JCAPTCHA Service类. 这个类完成读取配置(如何生成验证码, 生成的内容是什么, 等等), 并实际生成验证码. 同时也对外提供验证用户输入的功能.
3. 其它部分. 比如在UI层需要调用(1)来显示验证码图片, 在验证部分要调用(2)来测试输入的验证码是否正确.

下面是代码.

struts-config.xml:

<action path="/jcaptcha" type="test.CaptchaAction" />

与之对应的JSP部分. 很简单, 一个验证码图片, 一个输入框, 一个提交按钮.

<body>
<img src="<%= request.getContextPath() %>/jcaptcha.do"/>
<form method="POST" action="<%= request.getContextPath() %>/test.do?state=verify">
<input name="code"/>
<input type="submit"/>
</form>
</body>

加入一个Action, 作用就是生成图片(写成一个Servlet也能完成一样的功能). Java代码为:

public class CaptchaAction extends Action {


public ActionForward execute(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response)

throws Exception {

try {

CaptchaService.getInstance().writeCaptcha(request, response);

} catch (Exception e) {

e.printStackTrace();

return mapping.findForward("fatal");

}

return super.execute(mapping, form, request, response);

}

}


下面的就是重要的单例类CaptchaService了. 这个类继承自ListImageCaptchaEngine, 是JCAPTCAH的针对图片验证的类. CaptchaService类中有三个方法比较重要:

1. private buildInitialFactories()

这个方法是ListImageCaptchaEngine的abstract方法的覆盖. 必须实现. 目的是初始化验证码生成器的参数.


protected void buildInitialFactories() {

try {

SimpleTextPaster parser = (new SimpleTextPaster(MIN_LENGTH, MAX_LENGTH, Color.black));

//RandomTextPaster parser = new RandomTextPaster(MIN_LENGTH, MAX_LENGTH, Color.black);



//UniColorBackgroundGenerator back = new UniColorBackgroundGenerator(IMAGE_WIDTH, IMAGE_HEIGHT);

GradientBackgroundGenerator back = (new GradientBackgroundGenerator(IMAGE_WIDTH, IMAGE_HEIGHT, Color.orange, Color.white));


Font f = Font.decode("Consolas");

RandomFontGenerator rf = (new RandomFontGenerator(MIN_FONT_SIZE, MAX_FONT_SIZE, new Font[]{f}));// to easy to read


WordGenerator words = new RandomWordGenerator("ABCDEFG");



WordToImage word2image = new ComposedWordToImage(rf, back, parser);

ImageCaptchaFactory factory = new GimpyFactory(words, word2image);

addFactory(factory);

} catch (Exception ex) {

ex.printStackTrace();

}

}


对上面程序的解释. 一个验证码图片由四部分组成: TestParser, BackgroundGenerator, FontGenerator, 以及WordGenerator


TestParser, 都是从类AbstractTextPaster继承来, 作用是生成验证码中的文字部分. 较常用的下面两种:

- SimpleTextPaster 简单的实现. 文字部分是从左到右顺序排列的. 构造函数的参数分别为最短长度, 最大长度和文字颜色.
- RandomTextPaster 带有随机式样的实现. 和SimpleTextPaster的参数类似. 不过生成的文字部分, 带有随机的样式(粗体,斜体等), 开始位置也是随机的(可能从图片中间开始排列)


BackgroundGenerator, 都是从AbstractBackgroundGenerator类继承而来. 作用是产生验证码图片的背景. 构造的时候传入的基本参数为图片的宽度和高度. 常用的实现为:

- UniColorBackgroundGenerator 单一背景色. 默认的是白色.

- GradientBackgroundGenerator 渐变背景色. 在构造的时候需要指定起始颜色和终止颜色.


FontGenerator, 从AbstractFontGenerator 继承来. 但是和上面两种不同, 基本上在应用中使用的都是RandomFontGenerator. 构造的时候, 传入字体的最小值, 最大值, 以及字体列表的名称. 在生成验证码的时候, 随机选择某种字体, 某种大小来生成. 这里有几个问题需要注意:


FontGenerator字体的大小, TestParser中验证码的个数, 以及BackgroundGenerator中图片的大小需要匹配. 如果设置的不合理, 比如字体过大, 生成若干个验证码后发现图片的大小太小装不下了, 将出现异常.


在选择字体的时候, 下面两种名称开头字体是JCAPTCHA禁止使用的: Courier 和 Times Roman . 因为这两种字体用的太广泛了, 同时也是比较标准的字体, JCAPTCHA认为它们不够安全(容易被识别程序识别出来), 所以将忽略这些字体. 如果没有设置其他的字体的话, 会报错(找不到可用的字体).


WordGenerator: 生成文字的选项. 基本上用的都是RandomWordGenerator随机生成. 构造参数为可选的列表. 生成验证码的时候, 将从这些文字中选择.


这四项确定以后, 就可以生成一个factory, 加入生成列表里面去:


WordToImage word2image = new ComposedWordToImage(rf, back, parser);

ImageCaptchaFactory factory = new GimpyFactory(words, word2image);

addFactory(factory);


同样的, 可以有多个TestParser, BackgroundGenerator, FontGenerator, 生成多个factory, 这样每次生成验证码图片的时候都会更加不同.


2. writeCaptcha方法. 该方法的作用就是实际将图片写入response, 最终显示到浏览器上.

没什么特殊的. 唯一一点是, 通过setAttribute方法在当前的session中保存了验证码.


public void writeCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
//保存验证码
imageCaptcha = getNextImageCaptcha();
HttpSession session = request.getSession();
session.setAttribute(KEY, imageCaptcha);
BufferedImage image = (BufferedImage) imageCaptcha.getChallenge();

OutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");

JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(outputStream);
encoder.encode(image);

outputStream.flush();
} catch (IOException ex) {
throw ex;
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException ex) {
}
}
imageCaptcha.disposeChallenge();
}
}


3. 验证方法. 验证用户输入的验证码是否正确.
很简单的一个方法.

public boolean validateCaptcha(String validateCode, HttpSession session) {
try {
if (session == null) {
return false;
}
imageCaptcha = (ImageCaptcha) session.getAttribute(KEY);
if (imageCaptcha == null) {
return false;
}
validateCode = validateCode.toUpperCase();
boolean flag = (imageCaptcha.validateResponse(validateCode)).booleanValue();
session.removeAttribute(KEY);
return flag;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}


剩下的 Action 部分, 加入判断:


public ActionForward verify(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
String code = request.getParameter("code");
boolean b = CaptchaService.getInstance().validateCaptcha(code, request.getSession());
if (b) {

// 正确

} else {

// 错误

}
}


P.S: 中文的验证码是能够实现的, 只需要把上面的代码中字体一部分改成

Font f = Font.decode("华文细黑"); //当然其他字体也可以, 但是必须是Server上有的字体

WordGenerator words = new RandomWordGenerator("一二三四五六");
 类似资料: