使用JCaptcha生成验证码
在项目里用JCaptcha添加了验证码. 中间调试费了很多时间, 这里直接把步骤和结果的源代码贴出来吧.
首先要到JCaptcha的官网去下载jar包, 目前使用 jcaptcha-1.0-all.jar 这个就够了, 如果你的lib中没有 commons-collections-3.2.1.jar 的话, 需要去 Apache Collections 网站下一个, 因为生成时使用的fasthashmap依赖于这个jar.
JCaptcha 官网上 java doc已经已经更新至2.0alpha版, 但是发布的jar还是1.0, 所以java doc中提到的一些engine在jar中是没有的, 一些deprecated的类在1.0中依然需要使用.
JCaptcha 官网上提供了一个最简单的5分钟速成样例, 直接使用DefaultManageableImageCaptchaService构建验证图.
因为默认的验证图很难辨认, 所以还是需要自己定制一下, 以适应自己项目的需求.
首先是扩展出自己的ImageCaptchaEngine, 命名为 CaptchaImageEngine.java . 这里借鉴了几个网络上前辈提供的例子, 做了一些修改
public class CaptchaImageEngine extends ListImageCaptchaEngine { public static final Integer WORD_MIN_LENGTH = new Integer(4); public static final Integer WORD_MAX_LENGTH = new Integer(4); public static final Integer IMAGE_WIDTH = new Integer(160); public static final Integer IMAGE_HEIGHT = new Integer(50); public static final Integer FONT_MIN_SIZE = new Integer(30); public static final Integer FONT_MAX_SIZE = new Integer(35); protected void buildInitialFactories() { WordGenerator wordGenerator = (new RandomWordGenerator("23456789ABCDEFGHJKMNPQRSTUVWXY")); BackgroundGenerator backgroundGenerator = new UniColorBackgroundGenerator(IMAGE_WIDTH, IMAGE_HEIGHT); Font[] fontsList = new Font[]{Font.decode("Arial"), Font.decode("Georgia"), Font.decode("Verdana"), Font.decode("Courier New")}; FontGenerator fontGenerator = new RandomFontGenerator(FONT_MIN_SIZE, FONT_MAX_SIZE, fontsList); RandomRangeColorGenerator cgen = new RandomRangeColorGenerator( new int[] { 0, 128 }, new int[] { 0, 128 }, new int[] { 0, 196 } ); TextDecorator[] textdecorators = new TextDecorator[]{new BaffleTextDecorator(new Integer(1), Color.WHITE)}; TextPaster textPaster = new DecoratedRandomTextPaster( WORD_MIN_LENGTH, WORD_MAX_LENGTH, cgen, textdecorators ); WaterFilter water = new WaterFilter(); water.setAmplitude(5d);//振幅 water.setAntialias(true);//锯齿或平滑 water.setPhase(30d);//相位 water.setWavelength(60d); WordToImage wordToImage = new DeformedComposedWordToImage( fontGenerator, backgroundGenerator, textPaster, new ImageDeformationByFilters(new ImageFilter[]{}), new ImageDeformationByFilters(new ImageFilter[]{}), new ImageDeformationByFilters(new ImageFilter[]{water}) ); addFactory(new GimpyFactory(wordGenerator, wordToImage)); } }
然后是提供生成验证图的静态方法的 CaptchaService.java . 这里就可以使用刚才建的ImageEngine来绘制验证图了
public class CaptchaService { private static ImageCaptchaService instance = new DefaultManageableImageCaptchaService( new FastHashMapCaptchaStore(), new CaptchaImageEngine(), 180, 100000, 75000 ); private CaptchaService(){} public static BufferedImage getImageChallenge(String sid, Locale locale) { return instance.getImageChallengeForID(sid, locale); } public static BufferedImage getImageChallenge(String sid) { return instance.getImageChallengeForID(sid); } public static boolean validate(String sid, String input) { return instance.validateResponseForID(sid, input); } }
这样, 在action中就可以直接调用了, 调用的方法分为两部分, 一部分是生成图片
public String doGenerate() { try { // 获取session中的token用于生成验证图片, String token = getSession().getSession_token(); // 生成验证图片 BufferedImage challenge = CaptchaService.getImageChallenge(token); // 压缩为JPEG格式 ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream(); JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream); jpegEncoder.encode(challenge); // 置入action result的输出数据中 this.put_streambytes("image/jpeg", jpegOutputStream.toByteArray()); } catch (IllegalArgumentException e) { this.put_streambytes("image/jpeg", new byte[]{}); logger.info("IllegalArgumentException"); logger.debug("Exception Details", e); } catch (Exception e) { this.put_streambytes("image/jpeg", new byte[]{}); logger.info("UnknownException"); logger.debug("Exception Details", e); } return SUCCESS; }
然后, 用于处理action result的方法, 会使用 ServletOutputStream 输出图片
public class StreamProcessor { private static Logger logger = Logger.getLogger(RawProcessor.class); public static void process(HttpServletResponse res, String type, Object streamBytes) { try { res.setHeader("Cache-Control", "no-store"); res.setHeader("Pragma", "no-cache"); res.setDateHeader("Expires", 0); res.setContentType(type); ServletOutputStream responseOutputStream = res.getOutputStream(); responseOutputStream.write((byte[])streamBytes); responseOutputStream.flush(); responseOutputStream.close(); } catch (Exception e) { logger.info("Unknown Exception"); logger.debug("Exception details:", e); } } }
另一方面, 需要在页面上添加对图片的引用, 因为需要制造点击刷新的效果, 使用javascript来生成html引用代码.
前端的HTML是 (
其中使用到的回调函数是
function captcha_image(element_id, src) { var joiner = '&'; if (src.indexOf('?')== -1) joiner = '?'; $("#"+element_id).html(' '); }
后端响应这段js的action, 其作用, 是返回一段js代码, 让浏览器端去调用captcha_image()这个方法来生成引用图片的HTML代码
public String doJsInclude() { DefaultRequestBean bean = new DefaultRequestBean(); String elementId = bean.get("element"); String output = "captcha_image(\""+elementId+"\",\""+baseLink("captcha_image")+"\");"; this.put_raw_string(output); return SUCCESS; }
在用户提交后, 用于验证用户提交的验证码内容的方法片段:
String token = getSession().getSession_token(); if (!CaptchaService.validate(token, this.captcha_input.toUpperCase())) { flag = false; this.put_error_msg("captcha_input", langRes.get("login_success")); } return flag;