1. 功能描述

拖动滑块至图片缺口,完成验证。 图片及滑块形状可自定义。

图 滑动验证码演示

2. 实现步骤

2.1 设计思路

2.1.1 原理

1.将左图通过Java转换成右图

图 图片转换

2.用户在前端完成拖动后,将滑块最终位置信息发送给后端,来判断是否完成拼图。

2.1.2 UML 设计

1. 项目架构图如下所示。核心类为VerificationCode 与 Verifier,功能分别为生产验证码及验证用户拖动结果。

图 项目架构u图

2.拼图验证码核心组成为模版路径提供者及图片工具类。

图 VerificationCode 主要组成

2.2 主要代码

1.根据模版形状抠图

/*** 抠图** @param mainImage*        主图* @param templateImage*        抠图模版* @param x*        x轴位置* @param y*        y轴位置* @return 抠图*/public static BufferedImage cutoutImage(BufferedImage mainImage, BufferedImage templateImage, int x, int y) {Shape imageShape = null;try {imageShape = getImageShape(templateImage);} catch (InterruptedException e) {throw new RuntimeException();}int templateWidth = templateImage.getWidth();int templateHeight = templateImage.getHeight();BufferedImage image = new BufferedImage(templateWidth, templateHeight, BufferedImage.TYPE_INT_ARGB);Graphics2D graphics = image.createGraphics();graphics.clip(imageShape);graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);graphics.setStroke(new BasicStroke(5, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));graphics.drawImage(mainImage, -x, -y , mainImage.getWidth(), mainImage.getHeight(), null);graphics.dispose();return image;
}/*** 获取图片形状(从"天爱有情 https://www.tianai.cloud/"处拷贝该方法)** @param img*         图片* @return 图片形状*/private static Shape getImageShape(Image img) throws InterruptedException {ArrayList<Integer> x = new ArrayList<>();ArrayList<Integer> y = new ArrayList<>();int width = img.getWidth(null);int height = img.getHeight(null);// 首先获取图像所有的像素信息PixelGrabber pgr = new PixelGrabber(img, 0, 0, -1, -1, true);pgr.grabPixels();int[] pixels = (int[]) pgr.getPixels();// 循环像素for (int i = 0; i < pixels.length; i++) {// 筛选,将不透明的像素的坐标加入到坐标ArrayList x和y中int alpha = (pixels[i] >> 24) & 0xff;if (alpha != 0) {x.add(i % width > 0 ? i % width - 1 : 0);y.add(i % width == 0 ? (i == 0 ? 0 : i / width - 1) : i / width);}}// 建立图像矩阵并初始化(0为透明,1为不透明)int[][] matrix = new int[height][width];for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {matrix[i][j] = 0;}}// 导入坐标ArrayList中的不透明坐标信息for (int c = 0; c < x.size(); c++) {matrix[y.get(c)][x.get(c)] = 1;}/** 逐一水平"扫描"图像矩阵的每一行,将透明(这里也可以取不透明的)的像素生成为Rectangle,* 再将每一行的Rectangle通过Area类的rec对象进行合并, 最后形成一个完整的Shape图形*/Area rec = new Area();int temp = 0;//生成Shape时是1取透明区域还是取非透明区域的flagint flag = 1;for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {if (matrix[i][j] == flag) {if (temp == 0) {temp = j;}} else {if (temp != 0) {rec.add(new Area(new Rectangle(temp, i, j - temp, 1)));temp = 0;}}}temp = 0;}return rec;
}

2.将一张图片粘合在另外一张图片上

/*** 图片覆盖** @param mainImage*          主图* @param coverImage*          覆盖图* @param x*          x轴位置* @param y*          y轴位置* @return*/
public static BufferedImage overlayImage(BufferedImage mainImage, BufferedImage coverImage,int x, int y) {Graphics2D graphics = mainImage.createGraphics();graphics.drawImage(coverImage,x,y,coverImage.getWidth(),coverImage.getHeight(),null);graphics.dispose();return mainImage;
}

3.代码优化

3.1 自定义图片模版

支持使用者在resource文件下自主添加模版, 模版要求为:[文件夹名称]: cutout.png, main.jpg); 默认使用自带模版。

/** 图片路径提供者 */public class TemplateFolderPathProvider {private final String[] templateFileNameList;private final Random random;private TemplateFolderPathProvider(){URL url = Thread.currentThread().getContextClassLoader().getResource(PuzzleParameter.getInstance().getTemplatePath());File file = null;try {file = new File(url.toURI());} catch (URISyntaxException e) {e.printStackTrace();}templateFileNameList = file.list();random = new Random();}private static TemplateFolderPathProvider instance = new TemplateFolderPathProvider();public static TemplateFolderPathProvider getInstance(){return instance;}public String randomTemplateFolderPath() {int pos = random.nextInt(templateFileNameList.length);return PuzzleParameter.getInstance().getTemplatePath() + templateFileNameList[pos];}}

3.2 图片优化

使用者自定义的模版在图片尺寸及分辨率上有差异,而我们返回给前端的则要是统一的尺码,同时也需把图片压缩到合适的大小。

图 使用者自定义模版可能带来的问题

问题解决方案:

1.统一宽度及高度,进行等比例缩放,对于高度超出的采取截取的方式,高度偏短的则报错;

2.统一主图宽度与抠图宽度的宽度比,抠图按原长宽比进行缩放。缩放后,若高度超过主图高 度,则报错;

/*** 获取主图** @param folderName*          主图文件夹路径* @return  主图*/
public static BufferedImage getMainImage(String folderName) {BufferedImage image = getImageFromResourcePath(folderName + "/" + puzzleParameter.getTemplateMainName());int width = puzzleParameter.getMainWidth();int height = puzzleParameter.getMainHeight();if((height / width) > (image.getHeight() / image.getWidth())) {throw new RuntimeException("主图高宽比小于:" + height /  width);}return optimizeImage(image,width,height);
}/*** 获取抠图** @param folderName*          抠图文件夹路径* @return 抠图*/
public static BufferedImage getCutoutImage(String folderName) {BufferedImage image = getImageFromResourcePath(folderName + "/" + puzzleParameter.getTemplateCutoutName());double width = puzzleParameter.getMainWidth() * puzzleParameter.getMainWithCutoutRate();double height = width * (image.getHeight() / image.getWidth());if(height > puzzleParameter.getMainHeight()) {throw new RuntimeException("抠图模版高宽比大于:" +  puzzleParameter.getMainHeight() / (puzzleParameter.getMainHeight() * puzzleParameter.getMainWithCutoutRate()));}return optimizeImage(image,width,height);
}/*** 优化图片** @param image*         待优化图片* @param width*          图片宽度* @param height*          图片高度** @return 优化后的图片*/
private static BufferedImage optimizeImage(BufferedImage image, double width, double height) {BufferedImage newImage = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);Graphics graphics = newImage.getGraphics();int tempHeight = (int)(width * image.getHeight() / image.getWidth());graphics.drawImage(image.getScaledInstance((int)width, tempHeight, Image.SCALE_SMOOTH), 0, 0, null);graphics.dispose();return newImage;
}

3.3 信息加密

在对滑动结果进行验证的时候,核心参数为: 抠图在主图X轴的百分比。在我们生成验证码时,这个参数可以存储在服务器上,也可以直接发送给前端,前端提交验证的时候再携带这个参数。 如果采取这个方案 ,则必须加密这个参数。

加密要求:1,不可篡改 ;2 不可在除服务器外的其他地方被解密;对比安全性、实现复杂难度、性能要求等方面后,我们选择的方案是AES对称加密。

public class Aes {private final String ALGORITHM_NAME = "AES";public static final Charset CHARSET_UTF_8 = StandardCharsets.UTF_8;private final Lock lock = new ReentrantLock();/*** 密钥*/private final SecretKey key;/*** 加密/解码器*/private final Cipher cipher;private Aes() {this.key = generateKey();try {cipher = Cipher.getInstance(ALGORITHM_NAME);} catch (Exception e) {throw new RuntimeException("加密/解码器 初始化失败");}}private static Aes instance = new Aes();public static Aes getInstance() {return instance;}/*** 生成密钥** @return 密钥*/private SecretKey generateKey() {KeyGenerator keyGenerator = null;try {SecureRandom random = SecureRandom.getInstance("SHA1PRNG");random.setSeed(SecureRandom.getSeed(16));keyGenerator = KeyGenerator.getInstance(ALGORITHM_NAME);keyGenerator.init(128,random);} catch (NoSuchAlgorithmException e) {throw new RuntimeException("没有该加密算法");}return keyGenerator.generateKey();}/*** 加密** @param content*          待加密内容* @return 加密内容 base64*/public String encrypt(String content) {lock.lock();try {cipher.init(Cipher.ENCRYPT_MODE,key);byte[] bytes = cipher.doFinal(content.getBytes());return java.util.Base64.getEncoder().encodeToString(bytes);} catch (Exception e) {throw new RuntimeException("加密失败");}finally {lock.unlock();}//解锁}/*** 解密** @param content*          待解密内容 Base64表示的字符串* @return 解密内容 字符串*/public String decrypt(String content) {lock.lock();//base64解码byte[] contentBytes = content.getBytes(CHARSET_UTF_8);try {cipher.init(Cipher.DECRYPT_MODE,key);byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(contentBytes));return new String(bytes,CHARSET_UTF_8);} catch (Exception e) {System.out.println(e);throw new RuntimeException("解密失败");} finally {lock.unlock();}//解锁}}

3.4 缓存

模版中的主图及抠图模版频繁的创建与压缩会给系统带来较大的压力,对于这些图片我们可以使用Map把它们保存起来。

private static Map<String, BufferedImage> images = new HashMap<>();/*** 获取主图** @param folderName*          主图文件夹路径* @return  主图*/
public static BufferedImage getMainImage(String folderName) {String key = folderName + "main";if(images.get(key) == null) {BufferedImage image = getImageFromResourcePath(folderName + "/" + puzzleParameter.getTemplateMainName());int width = puzzleParameter.getMainWidth();int height = puzzleParameter.getMainHeight();if((height / width) > (image.getHeight() / image.getWidth())) {throw new RuntimeException("主图高宽比小于:" + height /  width);}images.put(key,optimizeImage(image,width,height));}return images.get(key);
}

3.5 自定义参数

使用者可自主 设置主图高度、宽度 及主图于抠图模版的比例等参数。

public class PuzzleParameter {/*** 默认模版在主图的开始位置:x坐标*/private final int DEFAULT_START_X = 5;/*** 默认模版在主图的开始位置:y坐标*/private final int DEFAULT_START_Y = 5;/*** 默认模版路径*/private final String DEFAULT_TEMPLATE_PATH = "puzzleVerificationCode/";/*** 默认主图模版名称*/private final String DEFAULT_TEMPLATE_MAIN_NAME = "main.jpg";/*** 默认抠图模版名称*/private final String DEFAULT_TEMPLATE_CUTOUT_NAME = "cutout.png";/*** 主图对抠图的比例*/private final double DEFAULT_MAIN_WITH_CUTOUT_RATE = 0.15;/*** 主图高度*/private final int DEFAULT_MAIN_HEIGHT = 300;/*** 主图宽度*/private final int DEFAULT_MAIN_WIDTH = 500;/*** 误差范围  + - 百分比*/private final double TOLERANCE_SCOPE = 0.01;private PuzzleParameter() {}private static volatile PuzzleParameter instance = null;public static synchronized PuzzleParameter getInstance() {if ( instance == null ) {instance = new PuzzleParameter();}return instance;}/*** 模版在主图的开始位置:x坐标*/private int startX = DEFAULT_START_X;/*** 模版在主图的开始位置:y坐标*/private int startY = DEFAULT_START_Y;/*** 模版路径 主图名称:main.png,抠图名称:cutout.png*/private String templatePath = DEFAULT_TEMPLATE_PATH;/** 误差范围  + - 百分比 */private double toleranceScope = TOLERANCE_SCOPE;/*** 主图模版名称*/private String templateMainName = DEFAULT_TEMPLATE_MAIN_NAME;/*** 抠图模版名称*/private String templateCutoutName = DEFAULT_TEMPLATE_CUTOUT_NAME;/*** 主图高度*/private int mainHeight = DEFAULT_MAIN_HEIGHT;/*** 主图宽度*/private int mainWidth = DEFAULT_MAIN_WIDTH;/*** 主图对抠图的比例*/private double mainWithCutoutRate = DEFAULT_MAIN_WITH_CUTOUT_RATE;
}

3.6 启动检查

在组件使用时,对组件的自定义参数、模版图片等进行自动检查。只执行一次检查。

static {Inspector inspector = new Inspector();//组件的自定义参数、模版图片等进行自动检查。只执行一次检查。inspector.doCheck();
}

3.7 工厂模式

为了以后能扩展其他验证码形式,设计模式采用抽象工厂模式。

图 抽象工厂模式

public interface VerificationCodeFactory {VerificationCode createVerificationCode();Verifier createVerifier();}

4 展望

4.1.轨迹检测

大厂的滑动验证码都具有轨迹检测的功能,通过机器学习来对滑动轨迹进行检测。

/*** 运动轨迹校验 还未实现** @param trackList*          运动轨迹* @return 检查结果*/
private boolean checkTrack(List<PuzzleVerifyInfo.TrackItem> trackList) {return true;
}

4.2 为 springboot 配置

使它成为springboot 工具包。通过配置文件来自定义参数,在springboot启动时,自动执行检查工作。

4.3.验证码扩展

后续将实现汉字排序 、物品辨别等验证码。

Java +Vue 实现滑动拼图验证码(Java篇 )相关推荐

  1. uniapp、vue实现滑动拼图验证码

    实际开发工作中,在登陆的时候需要短信验证码,但容易引起爬虫行为,需要用到反爬虫验证码,今天介绍一下拼图验证码,解决验证码反爬虫中的滑动验证码反爬虫. 原理 滑动拼图验证码是在滑块验证码的基础上增加了一 ...

  2. 滑动拼图验证码操作步骤:_拼图项目:一个不完整的难题

    滑动拼图验证码操作步骤: 马克·雷因霍尔德(Mark Reinhold)最近提议延迟Java 9,以花更多的时间完成项目Jigsaw,这是即将发布的版本的主要功能. 虽然这个决定肯定会使Java的厄运 ...

  3. 滑动拼图验证码操作步骤:_拼图项目:延期的后果

    滑动拼图验证码操作步骤: Mark Reinhold先生于2012年7月宣布 ,他们计划从Java 8撤消Jigsaw项目 ,因为Jigsaw计划于2013年9月(从现在开始一年)推迟其发布. 这个日 ...

  4. Android 滑动拼图验证码控件

    Android 滑动拼图验证码控件 简介: 很多软件为了安全防止恶意攻击,会在登录/注册时进行人机验证,常见的人机验证方式有:谷歌点击复选框进行验证,输入验证码验证,短信验证码,语音验证,文字按顺序选 ...

  5. 小视频app源码,Android 滑动拼图验证码控件

    小视频app源码,Android 滑动拼图验证码控件 代码实现: 滑块视图类:SlideImageView.java.实现小视频APP源码随机选取拼图位置,对拼图位置进行验证等功能. public c ...

  6. html 滑动拼图验证,vue登录滑动拼图验证

    vue登录滑动拼图验证 vue登录滑动拼图验证 一.安装插件: npm install --save vue-monoplasty-slide-verify 二.main.js引入: import S ...

  7. 爬虫之极验验证码破解-滑动拼图验证码破解

    滑动拼图验证码破解 前言 步骤分析 第一步,获取原图 第二步 拼接图片 第三步 计算豁口所在位置 第四步 计算拖动距离 模拟拖动 其他 前言 滑动验证码已经流行很多年了,我们在这里尝试一下如何实现滑动 ...

  8. js php滑动拼图解锁,js 滑动拼图验证码

    以前的验证码很简单,就是一个带些背景色或背景图和干扰线的纯数字字母类的验证码,现在已经发展变得很丰富了.我见过的就有好几种:纯字母数字类,数学计算类,依次点击图片上的文字类,从下列图片列表里选取符合描 ...

  9. 滑动拼图验证码 免费 java_js+canvas实现滑动拼图验证码功能

    上图为网易云盾的滑动拼图验证码,其应该有一个专门的图片库,裁剪的位置是固定的.我的想法是,随机生成图片,随机生成位置,再用canvas裁剪出滑块和背景图.下面介绍具体步骤. 首先随便找一张图片渲染到c ...

  10. php滑动拼图验证,JS怎么实现滑动拼图验证码

    这次给大家带来JS怎么实现滑动拼图验证码,JS实现滑动拼图验证码的注意事项有哪些,下面就是实战案例,一起来看一下. 上图为网易云盾的滑动拼图验证码,其应该有一个专门的图片库,裁剪的位置是固定的.我的想 ...

最新文章

  1. VsCode中配置git
  2. mongodb报错:E QUERY [thread1] SyntaxError: missing ; before statement @(shell):1:4
  3. A.M. Deviation 思维
  4. Spring Boot的学习之路(03):基础环境搭建,做好学习前的准备工作
  5. Storm Bolt接口
  6. iptables命令结构之命令
  7. 201771010125王瑜《面向对象程序设计(Java)》第六周学习总结
  8. python字符编码
  9. JQuery筛选选择器之内容筛选
  10. Redis使用单线程却快到飞起的原因
  11. python指数运算函数_分享Python中用于计算指数的exp()方法实例教程
  12. Android学习导航线路
  13. 我的云之旅--hadoop单机设置(2)
  14. package.json详解
  15. 双反馈直流matlab仿真,双闭环直流调速系统的课程设计(MATLAB仿真)
  16. GitHub上史上最全的iOS开源项目分类汇总
  17. 把字符串中的全角数字替换成半角数字 0123456789 转换成 0123456789
  18. 全球100位人工智能名人和2500名资深AI人士,将聚首深圳
  19. 原来MAC地址还是要购买的
  20. Ubuntu18.04 下虚拟机vm16pro 无法连接WIFI问题解决

热门文章

  1. WIFI远程控制实例分享,喜欢你就来!
  2. Transformers in NLP (一):图说transformer结构
  3. html5音乐播放器歌词显示,(1)H5实现音乐播放器【正在播放-歌词篇】
  4. Junglescout 正版账号共享 亚马逊卖家选品必备软件 junglescout插件同步升级
  5. STM32F107VCTx I2C通信
  6. 大学物理公式和名词整理
  7. 步科触摸屏程序上传 程序解密步骤方法
  8. 人脸检测——FDDB数据集评估
  9. 缺陷管理工具JIRA和禅道对比
  10. 指派问题程序c语言,指派问题lingo程序样例