时间:2017年07月09日星期日
说明:本文部份内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:无
学习源码:https://github.com/zccodere/s...javascript
主要内容html
验证码历史 课程内容 不一样方案对比 设计与实现 总结
验证码历史java
无验证码:垃圾骚扰 Luis von Ahn:Captcha 不断的升级 去验证码
常见验证码node
完成相似最后一张图片的验证码设计与实现git
对比方案 完成设计 编码实现 结果演示
结果演示github
不一样方案对比(一)web
浏览器请求验证码图片 服务器返回验证码图片及图片标识 浏览器提交验证码 服务器验证图片内容及标识
不一样方案对比(二)spring
浏览器请求验证码图片 服务器返回验证码图片及图片标识 浏览器提交验证码 图片文字/计算结果等 坐标 服务器验证 验证图片内容及标识 验证坐标及标识
设计与实现数据库
包结构 --controller、generator 主要类及做用 --Image:生成验证码图片核心类 --BufferedImageWrap:图片包装类 --ImageGroup:原始图片分组 --GenerateImageGroup:单次验证使用图片组 --Cache:单次验证数据缓存 --LoginController
程序设计:技术选择
教学使用数组
SpringMVC JSP Spring(4.0.5)
学习使用
SpringBoot Freemarker
思路整理
每次显示几张图片:由8张小图组成的一张大图 答案图片位置 选中位置坐标 坐标验证 先后关联
部分代码演示:源码请到个人github地址查看
login.html
<html> <head> <title>登陆</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript"> var index = 1; function addImg(e){ var parentDiv = document.getElementById("insert"); var topValue = 0,leftValue = 0; var obj = parentDiv; while(obj){ leftValue += obj.offsetLeft; topValue += obj.offsetTop; obj = obj.offsetParent; } e = e || window.event; var left = e.clientX + document.body.scrollLeft - document.body.clientLeft - 10; var top = e.clientY + document.body.scrollTop - document.body.clientTop - 10; var imgDivId = "img_" + index++; var newDiv = document.createElement("div"); parentDiv.appendChild(newDiv); newDiv.id = imgDivId; newDiv.style.position = "relative"; newDiv.style.zIndex = index; newDiv.style.width = "20px" newDiv.style.height = "20px"; newDiv.style.top = top - topValue - 150 + 10 + "px"; newDiv.style.left = left - leftValue -300 + "px"; newDiv.style.display = "inline"; newDiv.setAttribute("onclick","removeSelf('"+imgDivId+"');"); var img = document.createElement("img"); newDiv.appendChild(img); img.src = "img/a.png"; img.style.width = "20px"; img.style.height = "20px"; img.style.top = "0px"; img.style.left = "0px"; img.style.position = "absolute"; img.style.zIndex = index; } function removeSelf(id){ document.getElementById("insert").removeChild(document.getElementById(id)); } function login(){ var parentDiv = document.getElementById("insert"); var nodes = parentDiv.childNodes; var result = ""; for(var i=0;i<nodes.length;i++){ var id = nodes[i].id; if(id && id.startsWith('img_')){ var top = document.getElementById(id).style.top; var left = document.getElementById(id).style.left; result = result + top.replace('px','') + ',' + left.replace('px','') + ';'; } } console.info(result.substr(0,result.length - 1)); document.getElementById('location').value = result.substr(0,result.length - 1); document.getElementById('loginForm').submit(); } </script> </head> <body> <form id="loginForm" action="dologin" method="post"> <input type="hidden" id="location" name="location" /> <span>邮箱/用户名/手机号</span> <br /> <input type="text" name="username" /> <br /> <span>密码</span> <br /> <input type="password" name="password" /> <br /> <span>选出图片中的"${tip}"</span> <br /> <div id="insert"> <img src="../targetImage/${file}" height="150" width="300" onclick="addImg()" /> </div> <br /> <input type="button" value="登陆" onclick="login()"/> </form> </body> </html>
LoginController类
package com.myimooc.identifying.controller; import java.io.IOException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import com.myimooc.identifying.generator.Image; import com.myimooc.identifying.generator.ImageResult; /** * 登陆控制器 * @author ZhangCheng on 2017-07-09 * */ @Controller public class LoginController { /** * 登陆主页 * @param model * @param request * @param response * @return */ @RequestMapping("/login") public String identify(Model model,HttpServletRequest request,HttpServletResponse response){ try{ ImageResult imageResult = Image.generateImage(); model.addAttribute("file", imageResult.getName()); model.addAttribute("tip", imageResult.getTip()); System.out.println(imageResult.getName() + imageResult.getTip()); Cookie cookie = new Cookie("note",imageResult.getUniqueKey()); response.addCookie(cookie); request.getSession().setAttribute(imageResult.getUniqueKey(), imageResult); }catch(Exception e){ System.out.println("获取图片失败"); e.printStackTrace(); } return "login"; } /** * 刷新图片 * * @param request * @return * @throws IOException */ @RequestMapping(value = "/getPng") @ResponseBody public String getPng(HttpServletRequest request) throws IOException{ ImageResult imageResult = Image.generateImage(); ((HttpServletRequest) request).getSession().setAttribute("imageResult", imageResult); return imageResult.getName() + "," + imageResult.getTip(); } /** * 验证消息 * * @param location * @param request * @param userName * @param password * @return */ @PostMapping("/dologin") @ResponseBody public String doLogin(String location, HttpServletRequest request, String userName, String password, RedirectAttributes redirectAttributes) { System.out.println("验证坐标:"+ location); Cookie[] cookies = ((HttpServletRequest) request).getCookies(); Cookie note = null; for (Cookie cookie : cookies) { if (cookie.getName().equals("note")) { note = cookie; break; } } if(null == note){ return "ERROR"; } ImageResult imageResult = (ImageResult)request.getSession().getAttribute(note.getValue()); if(validate(location,imageResult)){ return "OK"; } return "ERROR"; } /** * 验证是否正确 * @param locationString * @param imageResult * @return */ private boolean validate(String locationString, ImageResult imageResult) { String[] resultArray = locationString.split(";"); int[][] array = new int[resultArray.length][2]; for (int i = 0; i<resultArray.length;i++) { String[] temp = resultArray[i].split(","); array[i][0] = Integer.parseInt(temp[0]) + 150 - 10; array[i][1] = Integer.parseInt(temp[1]) + 300; } for(int i=0;i<array.length;i++){ int location = location(array[i][1],array[i][0]); System.out.println("解析后的坐标序号:" + location); if(!imageResult.getKeySet().contains(location)){ return false; } } return true; } private int location(int x, int y) { if(y >=0 && y<75){ return xLocation(x); }else if(y >=75 && y<=150){ return xLocation(x)+4; }else{ // 脏数据 return -1; } } private int xLocation(int x) { if(x >=0 && x<75){ return 0; }else if(x >=75 && x<150){ return 1; }else if(x >=150 && x<225){ return 2; }else if(x >=225 && x<=300){ return 3; }else{ // 脏数据 return -1; } } }
Image类
package com.myimooc.identifying.generator; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.UUID; import javax.imageio.ImageIO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 生成验证码图片核心类 * @author ZhangCheng on 2017-07-09 * */ public class Image { private static final Logger log= LoggerFactory.getLogger(Image.class); private static Map<String,ImageGroup> imageGroupMap=new HashMap<>(); private static Map<Integer,Map<String,ImageGroup>> countGroupMap=new HashMap<>(); /** * 功能:由小图生成一种大图 * @return * @throws IOException */ public static ImageResult generateImage()throws IOException{ // 初始化 initImageGroup(); log.debug("初始化完成"); GenerateImageGroup generateImageGroup = randomImageGroups(); List<BufferedImageWrap> images = new ArrayList<BufferedImageWrap>(); // 找到图片干扰项 for (ImageGroup group : generateImageGroup.getGroups()) { for (String imgName : group.getImages()) { images.add(new BufferedImageWrap(false,getBufferedImage(imgName))); } } // 找到图片答案项 for(String imgName : generateImageGroup.getKeyGroup().getImages()){ images.add(new BufferedImageWrap(true,getBufferedImage(imgName))); } return mergeImage(images,generateImageGroup.getKeyGroup().getName()); } /** * 功能:根据图片名称得到图片缓冲流 * @param imgName * @return * @throws IOException */ private static BufferedImage getBufferedImage(String imgName)throws IOException { String rootPath = Image.class.getClassLoader().getResource("sourceImage/").getPath(); String imgPath = rootPath + imgName; File file = new File(imgPath); return ImageIO.read(file); } /** * 功能:将小图合并成一种大图 * @param images * @param name * @return */ private static ImageResult mergeImage(List<BufferedImageWrap> imageWraps, String tip) { Collections.shuffle(imageWraps); // 原始图片宽200像素,高200像素 int width = 200; int high = 200; int totalWidth = width * 4; BufferedImage destImage = new BufferedImage(totalWidth,400,BufferedImage.TYPE_INT_RGB); int x1 = 0; int x2 = 0; int order = 0; List<Integer> keysOrderList = new ArrayList<Integer>(); StringBuilder keysOrder = new StringBuilder(); Set<Integer> keySet = new HashSet<Integer>(); for(BufferedImageWrap image : imageWraps){ int[] rgb = image.getBufferedImage().getRGB(0, 0, width, high, null, 0, width); if(image.isKey()){ keysOrderList.add(order); int x = (order % 4) * 200; int y = order < 4 ? 0:200; keySet.add(order); keysOrder.append(order).append("(").append(x).append(",").append(y).append(")|"); } if(order < 4 ){ // 设置上半部分的RGB destImage.setRGB(x1, 0, width,high,rgb,0,width); x1 += width; }else{ destImage.setRGB(x2, high, width,high,rgb,0,width); x2 += width; } order++; } keysOrder.deleteCharAt(keysOrder.length() - 1); System.out.println("答案位置:" + keysOrder); String fileName = UUID.randomUUID().toString().replaceAll("-", "") + ".jpeg"; String rootPath = Image.class.getClassLoader().getResource("static/targetImage/").getPath(); //String rootPath = Image.class.getClassLoader().getResource("sourceImage/").getPath(); log.info("根路径:{}",rootPath); String fileUrl = rootPath + fileName; // 保存图片 saveImage(destImage,fileUrl,"png"); ImageResult ir = new ImageResult(); ir.setName(fileName); ir.setKeySet(keySet); ir.setUniqueKey(fileName); ir.setTip(tip); return ir; } /** * 功能:将图片写入指定的路径 * @param destImage * @param fileUrl * @param string */ private static void saveImage(BufferedImage destImage, String fileUrl, String format) { File file=new File(fileUrl); log.debug(file.getAbsolutePath()); try { ImageIO.write(destImage,format,file); } catch (IOException e) { log.info("图片写入失败"); e.printStackTrace(); } } /** * 功能:随机生成图片答案和干扰组 * @return */ private static GenerateImageGroup randomImageGroups(){ List<ImageGroup> result = new ArrayList<ImageGroup>(); int num = random(0, imageGroupMap.size() - 1); String name = new ArrayList<String>(imageGroupMap.keySet()).get(num); ImageGroup keyGroup = imageGroupMap.get(name); Map<Integer,Map<String,ImageGroup>> thisCountGroupMap = new HashMap<>(countGroupMap); thisCountGroupMap.get(keyGroup.getCount()).remove(name); // 假设总量8个,每种名称图片只有2个或4个,为了逻辑简单些 int leftCount = 8 - keyGroup.getCount(); if(leftCount == 4){ if(new Random().nextInt() % 2 == 0){ List<ImageGroup> groups = new ArrayList<ImageGroup>(thisCountGroupMap.get(4).values()); if(groups.size() > 1){ num = random(0, groups.size() - 1); }else{ num = 0; } result.add(groups.get(num)); }else{ List<ImageGroup> groups = new ArrayList<ImageGroup>(thisCountGroupMap.get(2).values()); int num1 = random(0, groups.size() - 1); result.add(groups.get(num1)); int num2 = random(0, groups.size() - 1,num1); result.add(groups.get(num2)); } }else if(leftCount == 6){ if(new Random().nextInt() % 2 == 0){ List<ImageGroup> groups1 = new ArrayList<ImageGroup>(thisCountGroupMap.get(4).values()); int num1 = random(0, groups1.size() - 1); result.add(groups1.get(num1)); List<ImageGroup> groups2 = new ArrayList<ImageGroup>(thisCountGroupMap.get(2).values()); int num2 = random(0, groups2.size() - 1); result.add(groups2.get(num2)); }else{ List<ImageGroup> groups = new ArrayList<ImageGroup>(thisCountGroupMap.get(2).values()); int num1 = random(0, groups.size() - 1); result.add(groups.get(num1)); int num2 = random(0, groups.size() - 1,num1); result.add(groups.get(num2)); int num3 = random(0, groups.size() - 1,num1,num2); result.add(groups.get(num3)); } } return new GenerateImageGroup(keyGroup, result); } /** * 功能:初始化图片组。后期优化可从数据库获取 */ private static void initImageGroup(){ ImageGroup group1 = new ImageGroup("包包",4,"bao/1.jpg","bao/2.jpg","bao/3.jpg","bao/4.jpg"); ImageGroup group2 = new ImageGroup("老虎",4,"laohu/1.jpg","laohu/2.jpg","laohu/3.jpg","laohu/4.jpg"); ImageGroup group3 = new ImageGroup("糖葫芦",4,"tanghulu/1.jpg","tanghulu/2.jpg","tanghulu/3.jpg","tanghulu/4.jpg"); ImageGroup group4 = new ImageGroup("小慕",4,"xiaomu/1.jpg","xiaomu/2.jpg","xiaomu/3.jpg","xiaomu/4.jpg"); ImageGroup group5 = new ImageGroup("柚子",4,"youzi/1.jpg","youzi/2.jpg","youzi/3.jpg","youzi/4.jpg"); ImageGroup group6 = new ImageGroup("订书机",2,"dingshuji/1.jpg","dingshuji/2.jpg"); ImageGroup group7 = new ImageGroup("蘑菇",2,"mogu/1.jpg","mogu/2.jpg"); ImageGroup group8 = new ImageGroup("磁铁",2,"citie/1.jpg","citie/2.jpg"); ImageGroup group9 = new ImageGroup("土豆",4,"tudou/1.jpg","tudou/2.jpg","tudou/3.jpg","tudou/4.jpg"); ImageGroup group10 = new ImageGroup("兔子",4,"tuzi/1.jpg","tuzi/2.jpg","tuzi/3.jpg","tuzi/4.jpg"); ImageGroup group11 = new ImageGroup("仙人球",4,"xianrenqiu/1.jpg","xianrenqiu/2.jpg","xianrenqiu/3.jpg","xianrenqiu/4.jpg"); initMap(group1,group2,group3,group4,group5,group6,group7,group8,group9,group10,group11); } /** * 功能:初始化全部图片组 * @param groups */ private static void initMap(ImageGroup... groups) { for (ImageGroup group : groups) { imageGroupMap.put(group.getName(),group); if(!countGroupMap.containsKey(group.getCount())){ countGroupMap.put(group.getCount(),new HashMap<String,ImageGroup>()); } countGroupMap.get(group.getCount()).put(group.getName(),group); } } /** * 功能:生成随机整数 * @param min * @param max * @return */ private static int random(int min,int max){ Random random = new Random(); return random.nextInt(max - min + 1) + min; } /** * 功能:生成随机整数不在指定整数数组里 * @param min * @param max * @param not * @return */ private static int random(int min,int max,Integer... not){ int num = random(min,max); List<Integer> notList = Arrays.asList(not); while(notList.contains(num)){ num = random(min,max); } return num; } }
/** * 功能:将小图合并成一种大图 * @param images * @param name * @return */ private static ImageResult mergeImage(List<BufferedImageWrap> imageWraps, String tip) { Collections.shuffle(imageWraps); // 原始图片宽200像素,高200像素 int width = 200; int high = 200; int totalWidth = width * 4; BufferedImage destImage = new BufferedImage(totalWidth,400,BufferedImage.TYPE_INT_RGB); int x1 = 0; int x2 = 0; int order = 0; List<Integer> keysOrderList = new ArrayList<Integer>(); StringBuilder keysOrder = new StringBuilder(); Set<Integer> keySet = new HashSet<Integer>(); for(BufferedImageWrap image : imageWraps){ int[] rgb = image.getBufferedImage().getRGB(0, 0, width, high, null, 0, width); if(image.isKey()){ keysOrderList.add(order); int x = (order % 4) * 200; int y = order < 4 ? 0:200; keySet.add(order); keysOrder.append(order).append("(").append(x).append(",").append(y).append(")|"); } if(order < 4 ){ // 设置上半部分的RGB destImage.setRGB(x1, 0, width,high,rgb,0,width); x1 += width; }else{ destImage.setRGB(x2, high, width,high,rgb,0,width); x2 += width; } order++; } keysOrder.deleteCharAt(keysOrder.length() - 1); System.out.println("答案位置:" + keysOrder); String fileName = UUID.randomUUID().toString().replaceAll("-", "") + ".jpeg"; String rootPath = Image.class.getClassLoader().getResource("static/targetImage/").getPath(); //String rootPath = Image.class.getClassLoader().getResource("sourceImage/").getPath(); log.info("根路径:{}",rootPath); String fileUrl = rootPath + fileName; // 保存图片 saveImage(destImage,fileUrl,"png"); ImageResult ir = new ImageResult(); ir.setName(fileName); ir.setKeySet(keySet); ir.setUniqueKey(fileName); ir.setTip(tip); return ir; } /** * 功能:将图片写入指定的路径 * @param destImage * @param fileUrl * @param string */ private static void saveImage(BufferedImage destImage, String fileUrl, String format) { File file=new File(fileUrl); log.debug(file.getAbsolutePath()); try { ImageIO.write(destImage,format,file); } catch (IOException e) { log.info("图片写入失败"); e.printStackTrace(); } }
总结
验证码历史 不一样方案对比 设计与实现 总结