1、简述:jcaptcha是java生成图形验证码非常好的api,入手简单,配置简单,使用很少的代码完成了生成图形验证码功能
读过源码,我们知道jcaptcha通过sessionId来验证验证码的正确性
2、问题:tomcat服务器启动web应用后,会为每个用户生成一个sessionId,以此区别不同用户。由于现在的企业级web应用都比较大,通常会部署在多个tomcat服务器上,这样就产生了问题,A服务器生成的sessionId,在B服务器是没有的,假如用户在访问的是A服务器,接着可能是访问的B服务器,由于每个sessionId是根据web服务器来生成的,而服务器间不能共享互通。所以如果根据tomcat的sessionId来识别验证码正确性就出现了问题。
3、解决办法:思路,把sessionId放在redis等支持集群模式的缓存中,实现了共享互通。
pom.xml引入jar
com.octo.captcha
jcaptcha
1.0
org.springframework
spring-beans
com.jhlabs
imaging
org.apache.tomcat
tomcat-juli
javax.servlet
servlet-api
第一种实现方式:通过UUID生成sessionId,把sessionId存到redis中
java config方式配置bean
@Configuration
//@Import(BaseWebMvcConfig.class)
public class AppConfig extends BaseAppConfig {
}
public class BaseAppConfig {
@Bean
public CaptchaStore getCaptchaStore(){
return new FastHashMapCaptchaStore();
}
@Bean
public AbstractManageableImageCaptchaService captchaService(CaptchaStore captchaStore){
// GenericManageableCaptchaService service = new GenericManageableCaptchaService(cSRCaptchaEngine(),300,20000);
// System.out.println("------------GenericManageableCaptchaService 验证码 init--------------------");
return new AbstractManageableImageCaptchaService(captchaStore, cSRCaptchaEngine(), 60, 100000, 75000){};
}
public ListImageCaptchaEngine cSRCaptchaEngine() {
return new ListImageCaptchaEngine() {
@Override
protected void buildInitialFactories() {
System.out.println("---------CSRCaptchaEngine buildInitialFactories2------ ");
int minWordLength = 4;
int maxWordLength = 4;
int fontSize = 50;
int imageWidth = 152;
int imageHeight = 70;
WordGenerator wordGenerator = new RandomWordGenerator("0123456789abcdefghijklmnopqrstuvwxyz");
TextPaster randomPaster = new DecoratedRandomTextPaster(minWordLength, maxWordLength,
new RandomRangeColorGenerator(new int[] { 0, 150 }, new int[] { 0, 150 }, new int[] { 0, 150 }),
new TextDecorator[] { new LineTextDecorator(new Integer(1), Color.BLACK) });
/*
* TextPaster randomPaster = new
* DecoratedRandomTextPaster(minWordLength, maxWordLength, new
* RandomListColorGenerator(new Color[] { new Color(23, 170,
* 27), new Color(220, 34, 11), new Color(23, 67, 172) }), new
* TextDecorator[] {new LineTextDecorator(new Integer(2),
* Color.BLACK)});
*/
BackgroundGenerator background = new UniColorBackgroundGenerator(imageWidth, imageHeight, Color.gray);
/*
* 设置彩色背景
* BackgroundGenerator background = new
* FunkyBackgroundGenerator(new Integer( 260), new Integer(70));
*/
FontGenerator font = new RandomFontGenerator(fontSize, fontSize,
new Font[] { new Font("nyala", Font.BOLD, fontSize), new Font("Bell MT", Font.PLAIN, fontSize),
new Font("Credit valley", Font.BOLD, fontSize) });
ImageDeformation postDef = new ImageDeformationByFilters(new ImageFilter[] {});
ImageDeformation backDef = new ImageDeformationByFilters(new ImageFilter[] {});
ImageDeformation textDef = new ImageDeformationByFilters(new ImageFilter[] {});
WordToImage word2image = new DeformedComposedWordToImage(font, background, randomPaster, backDef,
textDef, postDef);
addFactory(new GimpyFactory(wordGenerator, word2image));
}
};
}
}
package com.jing.controller;
import java.io.IOException;
import java.util.UUID;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.jing.util.Constants;
import com.jing.util.JedisUtils;
import com.jing.util.SenderUtils;
import com.octo.captcha.service.captchastore.CaptchaStore;
import com.octo.captcha.service.image.AbstractManageableImageCaptchaService;
import redis.clients.jedis.JedisCluster;
@Controller
@RequestMapping("/userController")
public class UserController {
@Resource
private JedisCluster jedisCluster;
@Resource
private AbstractManageableImageCaptchaService captchaService;
@Resource
private CaptchaStore captchaStore;
/**
* @description: 发送图形验证码
* @author: skyler
* @time: 2016年7月16日 下午2:40:17
*/
@RequestMapping(value = "/send_captcha", method = RequestMethod.GET)
public void sendCaptcha(String times, HttpServletRequest request, HttpServletResponse response)
throws IOException {
String sessionId = UUID.randomUUID().toString();
SenderUtils.sendPictureCaptcha(captchaService, sessionId, request, response);
String key = "redis_picture_key:"+11;
JedisUtils.process(jedisCluster, jedis -> {
jedis.set(key, sessionId);
jedis.expire(key, Constants.PICTURE_CAPTCHA_EXPIRE_TIME);
return null;
});
}
public void validateCaptcha(String captcha) throws Exception {
String key = "redis_picture_key:"+11;
String sessionId = JedisUtils.process(jedisCluster, jedis -> {
return jedis.get(key);
});
if (sessionId == null) {
throw new Exception("work.user.pictureCaptcha.timeout");
}
// 校验用户输入端图形验证码是否正确
boolean isResponseCorrect = captchaService.validateResponseForID(sessionId, captcha.toLowerCase());
JedisUtils.process(jedisCluster, jedis -> {
return jedis.del(key);
});
if (!isResponseCorrect) {
throw new Exception("work.user.pictureCaptcha.wrong");
}
}
}
启动应用访问:localhost:port/appNmae/userController/send_captcha?time=1234567890,浏览器页面生成4位的验证码
第二种实现方式:重写CaptchaStore类,这个类是把sessionId存到HashMap中,而HashMap是不能在多个web服务器间共享的,所以要通过重写CaptchaStore把sessionId存到redis中,上代码
pom.xml和上面一下
java config方式配置bean,重写CaptchaStore在这里实现
package com.jing.web.config;
import redis.clients.jedis.JedisCluster;
public class BaseAppConfig {
@Resource
private JedisCluster jedisCluster;
//CaptchaService captchaService;
/**
* 自定义ackson ObjectMapper
*
* @return
*/
@Bean
public ObjectMapper jacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer