最近项目需要图形验证码,于是乎着手写了一个,代码如下:
需要引入如下jar包:
完事之后一切都ok,就是出了个很诡异的问题,只要我在firox里面输入获取图像验证码的url,在我的controller里面就会执行两次,在chrome和ie以及其他浏览器里面都没有这个问题,后来查了半天才知道这算是firfox的一个Bug,当从服务器获取静态小的图片时,就会有这个问题,必须满足两个条件:
/**
省略import
*/
@Controller
public class CaptchaController {
private static final Logger logger = Logger
.getLogger(CaptchaController.class);
private static final String appCaptchaCookieKey = "APPCAPTCHACOOKIE";
@Autowired
private Producer captchaProducer;
@Autowired
private RedisComponent redisComponent;
@Value("${CAPTCHA_EXPIRE_TIME}")
private int captchaExpireTime;
/**
* @param request
* @param response
* @throws IOException
*/
@RequestMapping(value = "/captcha.jpg", method = RequestMethod.GET)
public void getCaptcha(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control",
"no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
ServletOutputStream out = null;
try {
String uniqCode = UUID.randomUUID().toString().replaceAll("-", "");
logger.info("生成的图形验证码唯一键:"+uniqCode);
String capText = captchaProducer.createText();
// 存储验证码文字到redis
logger.info("生成的验证码文字:"+capText);
redisComponent.set(ITMIConstants.CAPTCHA_PREFIX+uniqCode, capText.toLowerCase(), captchaExpireTime, TimeUnit.SECONDS);
logger.info("成功存入redis的验证码:"+redisComponent.get(ITMIConstants.CAPTCHA_PREFIX+uniqCode));
//将uniqCode添加到用户cookie
addCookie(response, appCaptchaCookieKey, uniqCode);
//生成验证码图片并返回给页面
BufferedImage bi = captchaProducer.createImage(capText);
out = response.getOutputStream();
ImageIO.write(bi, "jpg", out);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != out) {
out.close();
}
}
}
/**
* 校验验证码
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "/checkCaptcha.shtml", method = RequestMethod.POST)
public void checkCaptcha(HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control",
"no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("application/json");
ResDto res = new ResDto();
String captcha = request.getParameter("captcha");
logger.info("入参-----验证码:"+captcha);
if (captcha == null || captcha.equals("")) {
res.setMsg("请求缺少必要的参数");
res.setSuccess(false);
}else{
String cookieUniqCode = getCookie(request,appCaptchaCookieKey);
logger.info("cookie中获取到的图形验证码唯一键:"+cookieUniqCode);
String redisCode = redisComponent.get(ITMIConstants.CAPTCHA_PREFIX+cookieUniqCode);
logger.info("redis中取到的验证码:"+redisCode);
if(redisCode == null || redisCode.equals("")){
res.setMsg("验证码已过期,请重新获取验证码");
res.setSuccess(false);
}else{
if(redisCode.equals(captcha.toLowerCase())){
res.setMsg("成功");
res.setSuccess(true);
}else{
res.setMsg("验证码错误");
res.setSuccess(false);
}
}
}
String resStr = JSON.toJSONString(res);
logger.info("返回结果:"+resStr);
try {
PrintWriter out = response.getWriter();
out.write(resStr);
} catch (Exception e) {
logger.error("检查验证码接口调用失败"+e.getMessage());
e.printStackTrace();
}
}
/**
* 添加cookie 浏览器关闭即失效
* @param response
* @param name
* @param value
*/
public static void addCookie(HttpServletResponse response, String name,String value){
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(-1);
cookie.setPath("/");
response.addCookie(cookie);
}
/**
* 根据name获取cookie值
*
* @param request
* @param name
* @return
*/
public static String getCookie(HttpServletRequest request, String name) {
Map<String, Cookie> cookieMap = getCookieMap(request);
Cookie cookie = cookieMap.get(name);
if (null != cookie) {
return cookie.getValue();
}
return null;
}
/**
* 根据请求request获取所有cookie
* @param request
* @return
*/
public static Map<String, Cookie> getCookieMap(HttpServletRequest request) {
Map<String, Cookie> cookieMap = new HashMap<String, Cookie>();
Cookie[] cookies = request.getCookies();
if (null != cookies) {
for (int i = 0; i < cookies.length; i++) {
cookieMap.put(cookies[i].getName(), cookies[i]);
}
}
return cookieMap;
}
/**
* 检查验证码返回实体
* @author 201510260176
*/
class ResDto{
private boolean success;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
}
这个借助了开源的kaptcha来生成验证码图片,好处是可以在xml进行配置生成的验证码图片外观,我的配置如下:
<bean name="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">
<property name="config">
<bean class="com.google.code.kaptcha.util.Config">
<constructor-arg type="java.util.Properties">
<props>
<prop key="kaptcha.textproducer.char.length">4</prop>
<prop key="kaptcha.textproducer.char.string">ABCD123456789EFGHIjkmnpqrstuvwxyz</prop>
<prop key="kaptcha.textproducer.font.size">36</prop>
<prop key="kaptcha.textproducer.char.space">16</prop>
<prop key="kaptcha.image.width">220</prop>
<prop key="kaptcha.noise.color">blue</prop>
<prop key="kaptcha.textproducer.font.names">宋体,微软雅黑</prop>
</props>
</constructor-arg>
</bean>
</property>
</bean>
需要引入如下jar包:
<!-- kaptcha -->
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>0.0.9</version>
</dependency>
完事之后一切都ok,就是出了个很诡异的问题,只要我在firox里面输入获取图像验证码的url,在我的controller里面就会执行两次,在chrome和ie以及其他浏览器里面都没有这个问题,后来查了半天才知道这算是firfox的一个Bug,当从服务器获取静态小的图片时,就会有这个问题,必须满足两个条件:
(1)获取静态图片(服务器返回静态图片)
(2)小的图片(到底多小?自己上网查)
我测试了一下,给配置的验证码图片大小改大了这个问题就没了,所以网上的说法还是很靠谱的,下面就来看看本质的原因是啥。