記錄一下使用jCaptcha生成圖形驗證碼的過程。
首先,使用jCaptcha需要導入其jar包,這裏我的項目是maven項目,我就直接在pom裏面導入了。
pom代碼:
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha-all</artifactId>
<version>1.0-RC6</version>
<exclusions>
<exclusion>
<groupId>quartz</groupId>
<artifactId>quartz</artifactId>
</exclusion>
<exclusion>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</exclusion>
<exclusion>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
</exclusion>
<exclusion>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</exclusion>
<exclusion>
<groupId>concurrent</groupId>
<artifactId>concurrent</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xmlParserAPIs</artifactId>
</exclusion>
</exclusions>
</dependency>
ok,jar包導入後開始配置spring,注入相關bean。包含了jcaptcha使用相關的service、文字產生器、字體、顏色、背景等等,以下爲相關配置。這裏關於驗證碼工廠我根據自身需求,重寫了jCaptcha的GimpyFactory類和Gimpy類。這樣驗證碼匹配的時候就可以實現忽略大小寫了。後面我會詳細說明一下如何重寫這兩個類。
spring配置:
<!-- 驗證碼服務 -->
<bean id="captchaService" class="com.octo.captcha.service.multitype.GenericManageableCaptchaService">
<constructor-arg index="0" ref="imageEngine" />
<constructor-arg type="int" index="1" value="180" /><!--超時時間 秒-->
<constructor-arg type="int" index="2" value="100000" /><!--最大併發數-->
</bean>
<!-- 圖片引擎 -->
<bean id="imageEngine" class="com.octo.captcha.engine.GenericCaptchaEngine">
<constructor-arg index="0">
<list>
<ref bean="captchaFactory" />
</list>
</constructor-arg>
</bean>
<!-- 驗證碼工廠(此類爲重寫captcha內GimpyFactory類,爲了實現驗證碼不區分大小寫) -->
<bean id="captchaFactory" class="com.cpic.jvglapp.claimserver.entity.captcha.GimpyFactoryOverwrite">
<constructor-arg>
<ref bean="wordgen" />
</constructor-arg>
<constructor-arg>
<ref bean="wordtoimage" />
</constructor-arg>
</bean>
<!-- 文字產生器 -->
<bean id="wordgen" class="com.octo.captcha.component.word.wordgenerator.RandomWordGenerator">
<!--可選字符-->
<constructor-arg>
<value>0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</value>
</constructor-arg>
</bean>
<!-- 圖片生成器 -->
<bean id="wordtoimage" class="com.octo.captcha.component.image.wordtoimage.ComposedWordToImage">
<constructor-arg index="0">
<ref bean="fontGenRandom" />
</constructor-arg>
<constructor-arg index="1">
<ref bean="backGenUni" />
</constructor-arg>
<constructor-arg index="2">
<ref bean="decoratedPaster" />
</constructor-arg>
</bean>
<!-- 文字轉換圖片 -->
<bean id="fontGenRandom" class="com.octo.captcha.component.image.fontgenerator.RandomFontGenerator">
<!--最小字體-->
<constructor-arg index="0">
<value>20</value>
</constructor-arg>
<!--最大字體-->
<constructor-arg index="1">
<value>20</value>
</constructor-arg>
<constructor-arg index="2">
<list>
<bean class="java.awt.Font">
<constructor-arg index="0">
<value>Arial</value>
</constructor-arg>
<constructor-arg index="1">
<value>0</value>
</constructor-arg>
<constructor-arg index="2">
<value>20</value>
</constructor-arg>
</bean>
</list>
</constructor-arg>
</bean>
<bean id="backGenUni" class="com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator">
<!--背景寬度-->
<constructor-arg index="0">
<value>80</value>
</constructor-arg>
<!--背景高度-->
<constructor-arg index="1">
<value>32</value>
</constructor-arg>
</bean>
<bean id="decoratedPaster" class="com.octo.captcha.component.image.textpaster.DecoratedRandomTextPaster">
<!--最大字符長度-->
<constructor-arg type="java.lang.Integer" index="0">
<value>4</value>
</constructor-arg>
<!--最小字符長度-->
<constructor-arg type="java.lang.Integer" index="1">
<value>4</value>
</constructor-arg>
<!--文本顏色-->
<constructor-arg index="2">
<ref bean="colorGen" />
</constructor-arg>
<!--文本混淆-->
<constructor-arg index="3">
<list>
<!--<ref bean="baffleDecorator"/>-->
</list>
</constructor-arg>
</bean>
<bean id="baffleDecorator" class="com.octo.captcha.component.image.textpaster.textdecorator.BaffleTextDecorator">
<constructor-arg type="java.lang.Integer" index="0">
<value>1</value>
</constructor-arg>
<constructor-arg type="java.awt.Color" index="1">
<ref bean="colorWrite" />
</constructor-arg>
</bean>
<bean id="colorGen" class="com.octo.captcha.component.image.color.SingleColorGenerator">
<constructor-arg type="java.awt.Color" index="0">
<ref bean="colorDimGrey" />
</constructor-arg>
</bean>
<bean id="colorWrite" class="java.awt.Color">
<constructor-arg type="int" index="0">
<value>255</value>
</constructor-arg>
<constructor-arg type="int" index="1">
<value>255</value>
</constructor-arg>
<constructor-arg type="int" index="2">
<value>255</value>
</constructor-arg>
</bean>
<bean id="colorDimGrey" class="java.awt.Color">
<constructor-arg type="int" index="0">
<value>105</value>
</constructor-arg>
<constructor-arg type="int" index="1">
<value>105</value>
</constructor-arg>
<constructor-arg type="int" index="2">
<value>105</value>
</constructor-arg>
</bean>
然後,在實現類裏注入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());
}
}
這裏返回的是一張圖片,前端<img href="#" src="xxxxxxx/captcha.do"/>即可獲取到驗證碼圖片。
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));
}
}
最後別忘了在驗證碼工廠替換下類:
<!-- 驗證碼工廠(此類爲重寫captcha內GimpyFactory類,爲了實現驗證碼不區分大小寫) -->
<bean id="captchaFactory" class="com.cpic.jvglapp.claimserver.entity.captcha.GimpyFactoryOverwrite">
<constructor-arg>
<ref bean="wordgen" />
</constructor-arg>
<constructor-arg>
<ref bean="wordtoimage" />
</constructor-arg>
</bean>
寫完收工