时间:2017年07月09日星期日
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:无
学习源码:https://github.com/zccodere/s...
第一章:概述
1-1 课程概述
主要内容
验证码历史
课程内容
不同方案对比
设计与实现
总结
1-2 验证码历史
验证码历史
无验证码:垃圾骚扰
Luis von Ahn:Captcha
不断的升级
去验证码
常见验证码
1-3 项目介绍
完成类似最后一张图片的验证码设计与实现
对比方案
完成设计
编码实现
结果演示
结果演示
不同方案对比(一)
浏览器请求验证码图片
服务器返回验证码图片及图片标识
浏览器提交验证码
服务器验证图片内容及标识
不同方案对比(二)
浏览器请求验证码图片
服务器返回验证码图片及图片标识
浏览器提交验证码
图片文字/计算结果等
坐标
服务器验证
验证图片内容及标识
验证坐标及标识
设计与实现
包结构
--controller、generator
主要类及作用
--Image:生成验证码图片核心类
--BufferedImageWrap:图片包装类
--ImageGroup:原始图片分组
--GenerateImageGroup:单次验证使用图片组
--Cache:单次验证数据缓存
--LoginController
程序设计:技术选择
教学使用
SpringMVC
JSP
Spring(4.0.5)
学习使用
SpringBoot
Freemarker
思路整理
每次显示几张图片:由8张小图组成的一张大图
答案图片位置
选中位置坐标
坐标验证
前后关联
第二章:图片生成及页面显示
2-1 页面结构及LoginController介绍
部分代码演示:源码请到我的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;
}
}
}
2-2 如何生成图片generateImage
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;
}
}
2-3 如何将图片融合mergeImage
/**
* 功能:将小图合并成一种大图
* @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();
}
}
第三章:验证过程及总结
3-1 验证验证码过程及总结
总结
验证码历史
不同方案对比
设计与实现
总结
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。