1.配置Redis
配置参考:005.整合Spring session和Redis
2.添加Jcaptcha依赖
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha</artifactId>
<version>1.0</version>
</dependency>
3.spring-captcha.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:captcha.properties" order="3" ignore-unresolvable="true"/>
<!--验证码服务-->
<bean id="captchaService" class="com.airkisser.familyFinance.utils.jcaptcha.AirManageableCaptchaService">
<constructor-arg name="captchaEngine" ref="captchaEngine"/>
<constructor-arg name="captchaStore" ref="captchaStore"/>
<!--Captcha session过期时间,单位秒-->
<constructor-arg name="minGuarantedStorageDelayInSeconds"
value="${captcha.service.minGuarantedStorageDelayInSeconds}"/>
<!--最大并发数-->
<constructor-arg name="maxCaptchaStoreSize" value="${captcha.service.maxCaptchaStoreSize}"/>
<constructor-arg name="captchaStoreLoadBeforeGarbageCollection"
value="${captcha.service.captchaStoreLoadBeforeGarbageCollection}"/>
</bean>
<!--验证码存储管理容器:使用redis-->
<bean id="captchaStore" class="com.airkisser.familyFinance.utils.jcaptcha.AirRedisCaptchaStore">
<property name="redisTemplate" ref="sessionRedisTemplate"/>
</bean>
<!--图片引擎-->
<bean id="captchaEngine" class="com.octo.captcha.engine.GenericCaptchaEngine">
<constructor-arg name="factories">
<list>
<ref bean="gimpyFactory"/>
</list>
</constructor-arg>
</bean>
<!--验证码工厂-->
<bean id="gimpyFactory" class="com.octo.captcha.image.gimpy.GimpyFactory">
<constructor-arg name="generator" ref="wordGenerator"/>
<constructor-arg name="word2image" ref="wordToImage"/>
</bean>
<!--文字产生器-->
<bean id="wordGenerator" class="com.octo.captcha.component.word.wordgenerator.RandomWordGenerator">
<constructor-arg name="acceptedChars" value="${captcha.word.acceptedChars}"/>
</bean>
<bean id="wordToImage" class="com.octo.captcha.component.image.wordtoimage.ComposedWordToImage">
<constructor-arg name="fontGenerator" ref="fontGenerator"/>
<constructor-arg name="background" ref="backgroundGenerator"/>
<constructor-arg name="textPaster" ref="textPaster"/>
</bean>
<!--字体生成器-->
<bean id="fontGenerator" class="com.octo.captcha.component.image.fontgenerator.RandomFontGenerator">
<constructor-arg name="minFontSize" value="${captcha.font.minSize}"/>
<constructor-arg name="maxFontSize" value="${captcha.font.maxSize}"/>
<constructor-arg name="fontsList">
<list>
<ref bean="fontVerdana"/>
<ref bean="fontTahoma"/>
<ref bean="fontLucida"/>
<ref bean="fontComic"/>
<ref bean="fontArial"/>
</list>
</constructor-arg>
</bean>
<!--颜色生成器-->
<bean id="colorGenerator" class="com.octo.captcha.component.image.color.RandomRangeColorGenerator">
<constructor-arg name="redComponentRange">
<list>
<value>${captcha.color.range.red.min}</value>
<value>${captcha.color.range.red.max}</value>
</list>
</constructor-arg>
<constructor-arg name="greenComponentRange">
<list>
<value>${captcha.color.range.green.min}</value>
<value>${captcha.color.range.green.max}</value>
</list>
</constructor-arg>
<constructor-arg name="blueComponentRange">
<list>
<value>${captcha.color.range.blue.min}</value>
<value>${captcha.color.range.blue.max}</value>
</list>
</constructor-arg>
</bean>
<!--line colors-->
<bean id="lineColorGenerator" class="com.octo.captcha.component.image.color.RandomRangeColorGenerator">
<constructor-arg name="redComponentRange">
<list>
<value>${captcha.line.color.range.red.min}</value>
<value>${captcha.line.color.range.red.max}</value>
</list>
</constructor-arg>
<constructor-arg name="greenComponentRange">
<list>
<value>${captcha.line.color.range.green.min}</value>
<value>${captcha.line.color.range.green.max}</value>
</list>
</constructor-arg>
<constructor-arg name="blueComponentRange">
<list>
<value>${captcha.line.color.range.blue.min}</value>
<value>${captcha.line.color.range.blue.max}</value>
</list>
</constructor-arg>
</bean>
<!--背景生成器-->
<bean id="backgroundGenerator"
class="com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator">
<constructor-arg name="width" value="${captcha.bg.width}"/>
<constructor-arg name="height" value="${captcha.bg.height}"/>
<constructor-arg name="color" value="${captcha.bg.color}"/>
</bean>
<!--字符贴片-->
<bean id="textPaster" class="com.octo.captcha.component.image.textpaster.DecoratedRandomTextPaster">
<constructor-arg name="minAcceptedWordLength" value="${captcha.word.minLength}"/>
<constructor-arg name="maxAcceptedWordLength" value="${captcha.word.maxLength}"/>
<constructor-arg name="colorGenerator" ref="colorGenerator"/>
<constructor-arg name="decorators">
<list>
<ref bean="lineTextDecorator"/>
<ref bean="baffleTextDecorator"/>
</list>
</constructor-arg>
</bean>
<!--干扰线-->
<bean id="lineTextDecorator" class="com.octo.captcha.component.image.textpaster.textdecorator.LineTextDecorator">
<constructor-arg name="numberOfLinesPerGlyph" value="${captcha.line.numberOfLinesPerGlyph}"/>
<constructor-arg name="linesColorGenerator" ref="lineColorGenerator"/>
</bean>
<bean id="baffleTextDecorator"
class="com.octo.captcha.component.image.textpaster.textdecorator.BaffleTextDecorator">
<constructor-arg name="numberOfHolesPerGlyph" value="${captcha.baffle.numberOfHolesPerGlyph}"/>
<constructor-arg name="holesColorGenerator" ref="lineColorGenerator"/>
</bean>
<!--Fonts-->
<bean id="fontArial" class="java.awt.Font">
<constructor-arg index="0" value="Arial"/>
<constructor-arg index="1" value="${captcha.font.style}"/>
<constructor-arg index="2" value="${captcha.font.size}"/>
</bean>
<bean id="fontTahoma" class="java.awt.Font">
<constructor-arg index="0" value="Tahoma"/>
<constructor-arg index="1" value="${captcha.font.style}"/>
<constructor-arg index="2" value="${captcha.font.size}"/>
</bean>
<bean id="fontVerdana" class="java.awt.Font">
<constructor-arg index="0" value="Verdana"/>
<constructor-arg index="1" value="${captcha.font.style}"/>
<constructor-arg index="2" value="${captcha.font.size}"/>
</bean>
<bean id="fontComic" class="java.awt.Font">
<constructor-arg index="0" value="Comic sans MS"/>
<constructor-arg index="1" value="${captcha.font.style}"/>
<constructor-arg index="2" value="${captcha.font.size}"/>
</bean>
<bean id="fontLucida" class="java.awt.Font">
<constructor-arg index="0" value="Lucida console"/>
<constructor-arg index="1" value="${captcha.font.style}"/>
<constructor-arg index="2" value="${captcha.font.size}"/>
</bean>
</beans>
captcha.properties
# CaptchaService设置
# --Captcha session过期时间,单位秒
captcha.service.minGuarantedStorageDelayInSeconds=60
# --最大并发数
captcha.service.maxCaptchaStoreSize=100
captcha.service.captchaStoreLoadBeforeGarbageCollection=100
# 字体设置,字体style、size,size最大最小值
captcha.font.style=1
captcha.font.size=24
captcha.font.minSize=24
captcha.font.maxSize=28
# 验证码文本,生成文本选择的字符,最小和最大文本长度
captcha.word.acceptedChars=1234567890
captcha.word.minLength=4
captcha.word.maxLength=4
# 背景生成,背景图片宽度和高度以及背景颜色
captcha.bg.width=150
captcha.bg.height=42
captcha.bg.color=WHITE
# 干扰线,每个字符干扰线数量
captcha.line.numberOfLinesPerGlyph=1
captcha.baffle.numberOfHolesPerGlyph=1
# 颜色rgb Range
captcha.color.range.red.min=0
captcha.color.range.red.max=150
captcha.color.range.green.min=0
captcha.color.range.green.max=150
captcha.color.range.blue.min=0
captcha.color.range.blue.max=150
captcha.line.color.range.red.min=180
captcha.line.color.range.red.max=255
captcha.line.color.range.green.min=180
captcha.line.color.range.green.max=255
captcha.line.color.range.blue.min=180
captcha.line.color.range.blue.max=255
4.验证码存储器Redis实现
package com.airkisser.familyFinance.utils.jcaptcha;
import com.octo.captcha.Captcha;
import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.captchastore.CaptchaAndLocale;
import com.octo.captcha.service.captchastore.CaptchaStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import java.io.Serializable;
import java.util.Collection;
import java.util.Locale;
/**
* 基于Redis管理的CaptchaStore
*
* @author luojun at 2017/8/12,QQ: 1146874762
*/
public class AirRedisCaptchaStore implements CaptchaStore {
private static final Logger CAPTCHA_LOG = LoggerFactory.getLogger(AirRedisCaptchaStore.class);
private static final String CAPTCHA_SESSION_KEY = "com:airkisser:captcha";
private RedisTemplate<String, Serializable> redisTemplate;
private HashOperations<String, String, Serializable> hashOperations;
@Override
public boolean hasCaptcha(String sid) {
return this.hashOperations.hasKey(CAPTCHA_SESSION_KEY, sid);
}
@Override
public void storeCaptcha(String sid, Captcha captcha) throws CaptchaServiceException {
CAPTCHA_LOG.debug("Store captcha, Sid: {}.",sid);
this.hashOperations.put(CAPTCHA_SESSION_KEY, sid, captcha);
}
@Override
public void storeCaptcha(String sid, Captcha captcha, Locale locale) throws CaptchaServiceException {
CAPTCHA_LOG.debug("Store captcha, Sid: {}.",sid);
this.hashOperations.put(CAPTCHA_SESSION_KEY, sid, new CaptchaAndLocale(captcha, locale));
}
@Override
public boolean removeCaptcha(String sid) {
if (this.hasCaptcha(sid)) {
CAPTCHA_LOG.debug("Remove captcha, Sid: {}.",sid);
this.hashOperations.delete(CAPTCHA_SESSION_KEY, sid);
return true;
} else {
return false;
}
}
@Override
public Captcha getCaptcha(String sid) throws CaptchaServiceException {
Object val = this.hashOperations.get(CAPTCHA_SESSION_KEY, sid);
if (val == null) return null;
if (val instanceof Captcha) return (Captcha) val;
if (val instanceof CaptchaAndLocale) return ((CaptchaAndLocale) val).getCaptcha();
return null;
}
@Override
public Locale getLocale(String sid) throws CaptchaServiceException {
Object captchaAndLocale = this.getCaptcha(sid);
if (captchaAndLocale != null && captchaAndLocale instanceof CaptchaAndLocale) {
return ((CaptchaAndLocale) captchaAndLocale).getLocale();
}
return null;
}
@Override
public int getSize() {
CAPTCHA_LOG.debug("Get captcha size.");
return Math.toIntExact(this.hashOperations.size(CAPTCHA_SESSION_KEY));
}
@Override
public Collection getKeys() {
CAPTCHA_LOG.debug("Get captcha keys.");
return this.hashOperations.keys(CAPTCHA_SESSION_KEY);
}
@Override
@SuppressWarnings("unchecked")
public void empty() {
Collection<String> keys = this.getKeys();
if (!keys.isEmpty()) {
CAPTCHA_LOG.debug("Empty captcha.");
this.hashOperations.delete(CAPTCHA_SESSION_KEY, keys.toArray());
}
}
@Override
public void initAndStart() {
CAPTCHA_LOG.debug("InitAndStart captcha.");
this.empty();
}
@Override
public void cleanAndShutdown() {
CAPTCHA_LOG.debug("CleanAndShutdown captcha.");
this.empty();
}
@SuppressWarnings("unchecked")
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
this.hashOperations = this.redisTemplate.opsForHash();
}
}
5.每次验证验证码以后,无论成功与否,验证码服务默认会删除生成的验证信息,此处重写验证逻辑,修改为仅验证通过以后才删除验证信息
package com.airkisser.familyFinance.utils.jcaptcha;
import com.octo.captcha.Captcha;
import com.octo.captcha.engine.CaptchaEngine;
import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.captchastore.CaptchaStore;
import com.octo.captcha.service.multitype.GenericManageableCaptchaService;
import java.util.Locale;
/**
* GenericManageableCaptchaService扩展
*
* @author luojun at 2017/8/12,QQ: 1146874762
*/
public class AirManageableCaptchaService extends GenericManageableCaptchaService {
public AirManageableCaptchaService(CaptchaStore captchaStore, CaptchaEngine captchaEngine, int minGuarantedStorageDelayInSeconds, int maxCaptchaStoreSize, int captchaStoreLoadBeforeGarbageCollection) {
super(captchaStore, captchaEngine, minGuarantedStorageDelayInSeconds, maxCaptchaStoreSize, captchaStoreLoadBeforeGarbageCollection);
}
/**
* 重写validateResponseForID方法,默认每次经过该操作都会删除对应的Captcha
*
* @param ID SessionID
* @param response 提交的验证码参数值
*/
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);
if (valid) {//如果验证成功,移除Captcha
this.store.removeCaptcha(ID);
}
return valid;
}
/**
* 每次调用都重新生成
* @param ID
* @param locale
* @return
* @throws CaptchaServiceException
*/
public Object getChallengeForID(String ID, Locale locale) throws CaptchaServiceException {
Captcha captcha;
if(!this.store.hasCaptcha(ID)) {
this.store.removeCaptcha(ID);
}
captcha = this.generateAndStoreCaptcha(locale, ID);
if(captcha == null) {
captcha = this.generateAndStoreCaptcha(locale, ID);
} else if(captcha.hasGetChalengeBeenCalled()) {
captcha = this.generateAndStoreCaptcha(locale, ID);
}
Object challenge = this.getChallengeClone(captcha);
captcha.disposeChallenge();
return challenge;
}
}
6.验证码Controller
package com.airkisser.familyFinance.web;
import com.airkisser.familyFinance.core.result.BooleanResult;
import com.airkisser.familyFinance.core.result.Result;
import com.octo.captcha.service.multitype.GenericManageableCaptchaService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* JCaptcha验证码Controller
*
* @author luojun at 2017/8/11,QQ: 1146874762
*/
@Controller
@RequestMapping("/captcha")
public class CaptchaController {
private static final Logger LOGGER = LoggerFactory.getLogger(CaptchaController.class);
@Autowired
private GenericManageableCaptchaService captchaService;
// 生成验证码
@RequestMapping(method = RequestMethod.GET)
public void genCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
// the output stream to render the captcha image as jpeg into
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
// 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 = request.getSession().getId();
LOGGER.info("Gen Captcha Session Id:" + captchaId);
// call the ImageCaptchaService getChallenge method
BufferedImage challenge = captchaService.getImageChallengeForID(captchaId, request.getLocale());
// a jpeg encoder
// JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream);
// jpegEncoder.encode(challenge);
// byte[] captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
// flush it in the response
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
OutputStream outputStream = response.getOutputStream();
// outputStream.write(captchaChallengeAsJpeg);
ImageIO.write(challenge, "jpeg", outputStream);
outputStream.flush();
outputStream.close();
}
// 验证验证码
@RequestMapping(value = "/valid")
@ResponseBody
public Result validCaptcha(HttpServletRequest request, String captcha) {
if (StringUtils.isEmpty(captcha)) {
LOGGER.error("验证码不能为空");
return BooleanResult.errorResult("验证码不能为空", null);
}
String captchaId = request.getSession().getId();
LOGGER.info("Valid Captcha Session Id:" + captchaId);
if (!captchaService.validateResponseForID(captchaId, captcha)) {
LOGGER.error("验证码错误");
return BooleanResult.errorResult("验证码错误", null);
}
return BooleanResult.successResult("验证成功", null);
}
}