時間: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(); } }
總結
驗證碼歷史 不一樣方案對比 設計與實現 總結