图片验证码的原理
a) 生成图片验证码,当然生成验证码的具体实现可以放在另一工程中或者其他的服务器上(可以通过dubbo调用生成验证码服务);
b) 用户输入界面上的图片验证码
c) 进行服务端校验
2. 校验设备id值是否正确(DeviceId)已经解密(生成需要传入唯一标示,大多数是选择以设备id和当前时间作为唯一标示)
1、首先我们知道图片验证码都需要短时有效的,所以把验证码放到Redis中是不错的选择。
RedisObjectUtil redisObj=new RedisObjectUtil();
ReSubmitTokenList localTokenList=new ReSubmitTokenList(this.delayTime, this.tokenLength);设置验证码长度以及有效时间
2、生成token值
String str = localTokenList.genNextReSumbitTokenId();
public String genNextReSumbitTokenId(){
long l1 = ran.nextLong();
long l2 = (l1 >= 0L) ? l1 : -l1;
StringBuffer localStringBuffer = new StringBuffer(Long.toString(l2, 36));
while (localStringBuffer.length() < this.tokenLength)
localStringBuffer.insert(0, '0');
if (localStringBuffer.length() > this.tokenLength)
return localStringBuffer.substring(localStringBuffer.length() - this.tokenLength);
return localStringBuffer.toString();
}
在上面的方法中
StringBuffer(Long.toString(a, 36))第二个参数radix是进制数,范围是:2-36,把a转成36进制的字符串。
得到的字符串与规定的token长度比较。(小于规定长度插入0,大于取token长度子串)。
3、验证码是否区分大小写问题
if (区分大小写为真)
{
str = str.replaceAll("0", "1");
str = str.replaceAll("o", "p");
str = str.replaceAll("1", "2");
str = str.replaceAll("l", "m");
}
4、把生成的验证码数据,以及时间信息进行封装,并把这些信息加入到本地的token列表中
ReSumbitToken localTokenImpl = new ReSumbitToken(str, System.currentTimeMillis());5、把该验证码的唯一标示、本地的token列表、jedis池、过期时间信息存入到redis中,并把生成的验证码信息返回
redisObj.addRedisObject("BUYER_IMAGETOKEN_"+authToken, localTokenList, jedisSentinelPool, this.redisTimeOut);
return localTokenImpl;
6、把验证码数据绘制出来(输出方式为字节数组输出流)
TokenImgGenerator.createPic(localByteArrayOutputStream, imagetToken.getUniqueId());
7、绘制代码
public static void createPic(OutputStream paramOutputStream, String paramString)
throws IOException
{
try
{
int i = 15 * paramString.length() + 8;
int j = 25;
BufferedImage localBufferedImage = new BufferedImage(i, j, 1);
Graphics localGraphics = localBufferedImage.getGraphics();
Random localRandom = new Random();
localGraphics.setColor(new Color(217, 217, 255));
localGraphics.fillRect(0, 0, i, j);
localGraphics.setFont(new Font("Comic San MS", 0, 18));
localGraphics.setColor(getRandColor(160, 200));
for (int k = 0; k < 10; ++k)
{
int l = localRandom.nextInt(i);
int i1 = localRandom.nextInt(j);
int i2 = localRandom.nextInt(12);
int i3 = localRandom.nextInt(12);
localGraphics.drawOval(l, i1, l + i2, i1 + i3);
}
for (int z = 0; z < paramString.length(); ++z)
{
char l = paramString.charAt(z);
String str = String.valueOf(l);
localGraphics.setColor(new Color(20 + localRandom.nextInt(110), 20 + localRandom.nextInt(110), 20 + localRandom.nextInt(110)));
localGraphics.drawString(str, 15 * z + 6, 16);
}
localGraphics.dispose();
JPEGImageEncoder localJPEGImageEncoder = JPEGCodec.createJPEGEncoder(paramOutputStream);
localJPEGImageEncoder.encode(localBufferedImage);
}
catch (Exception localException)
{
a.error("The token's pic generate error.", localException);
}
}
这些就完成了验证码的显示,下面说下是如何验证的。
4、验证图片验证码(简单来说就是把验证码的唯一标示传入进来,去到缓存池查询这个key是否存在。)
1、配置图片验证码的拦截器
通过配置bean注入的方式把相关信息进行注入
2、验证图片验证码的实现类
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
验证图形验证码
if(urlMapping.containsKey(request.getServletPath())){
JedisSentinelUtil.Strings str = new JedisSentinelUtil(jedisSentinelPool).new Strings();
String flag=str.get(request.getParameter("mobileno")+"checkImgFlag");
String loginFlag=request.getParameter("loginFlag");
if("0".equals(loginFlag)||flag==null){
return true;
}
int state=valImgTokenService.verifyImgToken(request.getParameter("DeviceId"), request.getParameter("ImgToken"));
if(state!=1){
this.exceptionHandler.handler(request, response, 9, "图形验证码错误");
return false;
}
}
return true;
}
3、调验证接口需要传入设备id也就是生成验证码时的唯一标示以及输入验证码
4、对经处理的唯一标示进行校验(非空,解密校验)
5、验证
int state=this.imageTokenManager.verifyToken(deviceId, uniqueId);
6、验证实现类
public int verifyToken(String authToken,String uniqueId){
RedisObjectUtil redisObj=new RedisObjectUtil();
获取token值
JedisSentinelUtil.Strings str = new JedisSentinelUtil(jedisSentinelPool).new Strings();
JedisSentinelUtil.Keys keys=new JedisSentinelUtil(jedisSentinelPool).new Keys();
//加锁 防止并发问题
Long i=str.setnx(唯一标示, "LOCK_BUYER_IMAGETOKEN__"+authToken);
if(i==1){
try{
ReSubmitTokenList localTokenList=redisObj.getObject("BUYER_IMAGETOKEN_"+authToken, jedisSentinelPool, ReSubmitTokenList.class);
if (localTokenList == null)
return 0;
if (uniqueId == null)
return -2;
try{
if (this.ignoreCase){
uniqueId = uniqueId.toLowerCase();
}
ReSumbitToken localToken = localTokenList.get(uniqueId,this.ignoreCase);
if (localToken != null)
return 1;
return -3;
}catch (Exception localException){
log.error(""+localException);
}finally{
更新token列表
redisObj.addRedisObject("BUYER_IMAGETOKEN_"+authToken, localTokenList, jedisSentinelPool, this.redisTimeOut);
}
}finally{
解锁
keys.del("LOCK_BUYER_IMAGETOKEN__"+authToken);
}
}else{
return -1;
}
return -1;
// }
}
7、是否区分大小写问题
如果区分大小写
if (this.ignoreCase){
uniqueId = uniqueId.toLowerCase();
}
8、简单来说,就是通过传入唯一标示在redis中通过主键获得value,如果存在该值则校验成功,并更新本地tokenlist。
以上属于个人对验证码的简单理解,难免有失误的地方,还望各位猿友指出,不胜感激!