记录一下使用jCaptcha生成图形验证码的过程。
首先,使用jCaptcha需要导入其jar包,这里我的项目是maven项目,我就直接在pom里面导入了。
pom代码:
ok,jar包导入后开始配置spring,注入相关bean。包含了jcaptcha使用相关的service、文字产生器、字体、颜色、背景等等,以下为相关配置。这里关于验证码工厂我根据自身需求,重写了jCaptcha的GimpyFactory类和Gimpy类。这样验证码匹配的时候就可以实现忽略大小写了。后面我会详细说明一下如何重写这两个类。
spring配置:
然后,在实现类里注入jCaptcha的服务类,调用其提供的图片生成函数和校验函数就搞定了。
java代码(生成验证码):
@RequestMapping("/captcha")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {
try {
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
String captchaId = request.getSession().getId();
BufferedImage challenge = imageCaptchaService.getImageChallengeForID(captchaId, request.getLocale());
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0L);
response.setContentType("image/jpeg");
ImageIO.write(challenge, "jpeg", jpegOutputStream);
byte[] captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
ServletOutputStream respOs = response.getOutputStream();
respOs.write(captchaChallengeAsJpeg);
respOs.flush();
respOs.close();
} catch (IOException e) {
logger.error("generate captcha image error: {}", e.getMessage());
}
}
这里返回的是一张图片,前端即可获取到验证码图片。
java代码(校验验证码):
captchaFlag = imageCaptchaService.validateResponseForID(session.getId(), captcha);//校验验证码正确性
一行代码就搞定了,但是需要注意的是,jCaptcha自身提供的验证码校验是区分大小写的。
接来下,说明一下如何实现验证码不区分大小吧。
我们先来看一下jCaptcha的源码是怎么校验的,先看下代码:
public Boolean validateResponseForID(String ID, Object response)
throws CaptchaServiceException
{
if (!this.store.hasCaptcha(ID)) {
throw new CaptchaServiceException("Invalid ID, could not validate unexisting or already validated captcha");
}
Boolean valid = this.store.getCaptcha(ID).validateResponse(response);
this.store.removeCaptcha(ID);
return valid;
}
这是validateResponseForID的代码,this.store.hasCaptcha是判断该id下是否已经生成验证码,没有生成会抛出异常需要注意。然后校验是在this.store.getCaptcha(ID).validateResponse(reponse);接下来看看他是如何校验的。
看看代码:
public final Boolean validateResponse(Object response)
{
return (null != response) && ((response instanceof String)) ? validateResponse((String)response) : Boolean.FALSE;
}
private final Boolean validateResponse(String response)
{
return new Boolean(response.equals(this.response));
}
看到这里就很明显了,他是通过equals校验的。这个类是通过GimpyFactory来创建的,看看GimpyFactory的代码:
package com.octo.captcha.image.gimpy;
import com.octo.captcha.CaptchaException;
import com.octo.captcha.CaptchaQuestionHelper;
import com.octo.captcha.component.image.wordtoimage.WordToImage;
import com.octo.captcha.component.word.wordgenerator.WordGenerator;
import com.octo.captcha.image.ImageCaptcha;
import com.octo.captcha.image.ImageCaptchaFactory;
import java.awt.image.BufferedImage;
import java.security.SecureRandom;
import java.util.Locale;
import java.util.Random;
public class GimpyFactory
extends ImageCaptchaFactory
{
private Random myRandom = new SecureRandom();
private WordToImage wordToImage;
private WordGenerator wordGenerator;
public static final String BUNDLE_QUESTION_KEY = Gimpy.class.getName();
public GimpyFactory(WordGenerator generator, WordToImage word2image)
{
if (word2image == null) {
throw new CaptchaException("Invalid configuration for a GimpyFactory : WordToImage can't be null");
}
if (generator == null) {
throw new CaptchaException("Invalid configuration for a GimpyFactory : WordGenerator can't be null");
}
this.wordToImage = word2image;
this.wordGenerator = generator;
}
public ImageCaptcha getImageCaptcha()
{
return getImageCaptcha(Locale.getDefault());
}
public WordToImage getWordToImage()
{
return this.wordToImage;
}
public WordGenerator getWordGenerator()
{
return this.wordGenerator;
}
public ImageCaptcha getImageCaptcha(Locale locale)
{
Integer wordLength = getRandomLength();
String word = getWordGenerator().getWord(wordLength, locale);
BufferedImage image = null;
try
{
image = getWordToImage().getImage(word);
}
catch (Throwable e)
{
throw new CaptchaException(e);
}
ImageCaptcha captcha = new Gimpy(CaptchaQuestionHelper.getQuestion(locale, BUNDLE_QUESTION_KEY), image, word);//在此创建的Gimpy
return captcha;
}
protected Integer getRandomLength()
{
int range = getWordToImage().getMaxAcceptedWordLength() - getWordToImage().getMinAcceptedWordLength();
int randomRange = range != 0 ? this.myRandom.nextInt(range + 1) : 0;
Integer wordLength = new Integer(randomRange + getWordToImage().getMinAcceptedWordLength());
return wordLength;
}
}
最后,重写下GimpyFactory和Gimpy。
GimpyFactory重写代码:
public class GimpyFactoryOverwrite extends ImageCaptchaFactory
{
private Random myRandom = new SecureRandom();
private WordToImage wordToImage;
private WordGenerator wordGenerator;
public static final String BUNDLE_QUESTION_KEY = Gimpy.class.getName();
public GimpyFactoryOverwrite(WordGenerator generator, WordToImage word2image)
{
if (word2image == null) {
throw new CaptchaException("Invalid configuration for a GimpyFactory : WordToImage can't be null");
}
if (generator == null) {
throw new CaptchaException("Invalid configuration for a GimpyFactory : WordGenerator can't be null");
}
this.wordToImage = word2image;
this.wordGenerator = generator;
}
public ImageCaptcha getImageCaptcha()
{
return getImageCaptcha(Locale.getDefault());
}
public WordToImage getWordToImage()
{
return this.wordToImage;
}
public WordGenerator getWordGenerator()
{
return this.wordGenerator;
}
public ImageCaptcha getImageCaptcha(Locale locale)
{
Integer wordLength = getRandomLength();
String word = getWordGenerator().getWord(wordLength, locale);
BufferedImage image = null;
try
{
image = getWordToImage().getImage(word);
}
catch (Throwable e)
{
throw new CaptchaException(e);
}
ImageCaptcha captcha = new GimpyOverWrite(CaptchaQuestionHelper.getQuestion(locale, BUNDLE_QUESTION_KEY), image, word);
return captcha;
}
protected Integer getRandomLength()
{
int range = getWordToImage().getMaxAcceptedWordLength() - getWordToImage().getMinAcceptedWordLength();
int randomRange = range != 0 ? this.myRandom.nextInt(range + 1) : 0;
Integer wordLength = new Integer(randomRange + getWordToImage().getMinAcceptedWordLength());
return wordLength;
}
}
Gimpy重写代码:
public class GimpyOverWrite extends ImageCaptcha implements Serializable {
/**
*
*/
private static final long serialVersionUID = 4721070331461038498L;
private String response;
GimpyOverWrite(String question, BufferedImage challenge, String response)
{
super(question, challenge);
this.response = response;
}
public final Boolean validateResponse(Object response)
{
return (null != response) && ((response instanceof String)) ? validateResponse((String)response) : Boolean.FALSE;
}
private final Boolean validateResponse(String response)
{
return new Boolean(response.equalsIgnoreCase(this.response));
}
}
最后别忘了在验证码工厂替换下类:
写完收工