使用Java生成图片滑动验证码

image.png

目前接到了一个新的小需求,要在登录时进行滑动图片验证。

搜了一下网上的demo,没有太多很完整的demo。就参考各种文档自己拼凑了一个出来。整理一下,以便后续使用。

核心流程分析.

服务端随机生成滑块图片和带滑块抠图的背景图片,并保存滑块抠图的坐标位置,并对应一个唯一token。

服务端将生成的唯一token与抠图的Y轴坐标,以及抠图和被扣以后的原图返回给前端,供前端进行展示。

前端实现滑动交互,将抠图拼在抠图阴影之上,获取到用户滑动轨迹。

前端将滑动轨迹上报到服务端,服务端匹配是否与抠图坐标在允许的误差范围(这里单纯校验用户滑动距离是最基本的校验,出于更高的安全考虑,还会考虑用户滑动的整个轨迹,用户在当前页面的访问行为等。这些可以很复杂,甚至借助到用户行为数据分析模型,最终的目标都是增加非法的模拟和绕过的难度。本项目中仅考虑业务场景的实现,未进行风控过滤)

滑动验证通过后,返回给前端成功结果,或者按照正常流程继续进行,例如发送短信验证码、成功登录等操作。

以下代码主要呈现的是如何生成抠图及对抠图后图片进行处理的过程。

准备内容:

1、多张尺寸固定的图片,例如300×150。

2、确定好截取的尺寸,例如50×50。

以下是代码核心部分:

import cn.welkin.entity.VerifyImage;

import sun.misc.BASE64Decoder;

import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;

import java.awt.*;

import java.awt.image.BufferedImage;

import java.io.*;

import java.util.ArrayList;

import java.util.List;

import java.util.Random;

/**

* @Author WelKin

* @ClassName VerifyImageUtil

* @Description: TODO

* @Date 2019/06/13 10:26

* @Version 1.0

**/

public class VerifyImageUtil {

/**

* 源文件宽度

*/

private static int ORI_WIDTH = 300;

/**

* 源文件高度

*/

private static int ORI_HEIGHT = 150;

/**

* 模板图宽度

*/

private static int CUT_WIDTH = 50;

/**

* 模板图高度

*/

private static int CUT_HEIGHT = 50;

/**

* 抠图凸起圆心

*/

private static int circleR = 5;

/**

* 抠图内部矩形填充大小

*/

private static int RECTANGLE_PADDING = 8;

/**

* 抠图的边框宽度

*/

private static int SLIDER_IMG_OUT_PADDING = 1;

/**

* 根据传入的路径生成指定验证码图片

*

* @param filePath

* @return

* @throws IOException

*/

public static VerifyImage getVerifyImage(String filePath) throws IOException {

BufferedImage srcImage = ImageIO.read(new File(filePath));

int locationX = CUT_WIDTH + new Random().nextInt(srcImage.getWidth() - CUT_WIDTH * 3);

int locationY = CUT_HEIGHT + new Random().nextInt(srcImage.getHeight() - CUT_HEIGHT) / 2;

BufferedImage markImage = new BufferedImage(CUT_WIDTH,CUT_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR);

int[][] data = getBlockData();

cutImgByTemplate(srcImage, markImage, data, locationX, locationY);

return new VerifyImage(getImageBASE64(srcImage),getImageBASE64(markImage),locationX,locationY);

}

/**

* 生成随机滑块形状

*

* 0 透明像素

* 1 滑块像素

* 2 阴影像素

* @return int[][]

*/

private static int[][] getBlockData() {

int[][] data = new int[CUT_WIDTH][CUT_HEIGHT];

Random random = new Random();

//(x-a)²+(y-b)²=r²

//x中心位置左右5像素随机

double x1 = RECTANGLE_PADDING + (CUT_WIDTH - 2 * RECTANGLE_PADDING) / 2.0 - 5 + random.nextInt(10);

//y 矩形上边界半径-1像素移动

double y1_top = RECTANGLE_PADDING - random.nextInt(3);

double y1_bottom = CUT_HEIGHT - RECTANGLE_PADDING + random.nextInt(3);

double y1 = random.nextInt(2) == 1 ? y1_top : y1_bottom;

double x2_right = CUT_WIDTH - RECTANGLE_PADDING - circleR + random.nextInt(2 * circleR - 4);

double x2_left = RECTANGLE_PADDING + circleR - 2 - random.nextInt(2 * circleR - 4);

double x2 = random.nextInt(2) == 1 ? x2_right : x2_left;

double y2 = RECTANGLE_PADDING + (CUT_HEIGHT - 2 * RECTANGLE_PADDING) / 2.0 - 4 + random.nextInt(10);

double po = Math.pow(circleR, 2);

for (int i = 0; i < CUT_WIDTH; i++) {

for (int j = 0; j < CUT_HEIGHT; j++) {

//矩形区域

boolean fill;

if ((i >= RECTANGLE_PADDING && i < CUT_WIDTH - RECTANGLE_PADDING)

&& (j >= RECTANGLE_PADDING && j < CUT_HEIGHT - RECTANGLE_PADDING)) {

data[i][j] = 1;

fill = true;

} else {

data[i][j] = 0;

fill = false;

}

//凸出区域

double d3 = Math.pow(i - x1, 2) + Math.pow(j - y1, 2);

if (d3 < po) {

data[i][j] = 1;

} else {

if (!fill) {

data[i][j] = 0;

}

}

//凹进区域

double d4 = Math.pow(i - x2, 2) + Math.pow(j - y2, 2);

if (d4 < po) {

data[i][j] = 0;

}

}

}

//边界阴影

for (int i = 0; i < CUT_WIDTH; i++) {

for (int j = 0; j < CUT_HEIGHT; j++) {

//四个正方形边角处理

for (int k = 1; k <= SLIDER_IMG_OUT_PADDING; k++) {

//左上、右上

if (i >= RECTANGLE_PADDING - k && i < RECTANGLE_PADDING

&& ((j >= RECTANGLE_PADDING - k && j < RECTANGLE_PADDING)

|| (j >= CUT_HEIGHT - RECTANGLE_PADDING - k && j < CUT_HEIGHT - RECTANGLE_PADDING +1))) {

data[i][j] = 2;

}

//左下、右下

if (i >= CUT_WIDTH - RECTANGLE_PADDING + k - 1 && i < CUT_WIDTH - RECTANGLE_PADDING + 1) {

for (int n = 1; n <= SLIDER_IMG_OUT_PADDING; n++) {

if (((j >= RECTANGLE_PADDING - n && j < RECTANGLE_PADDING)

|| (j >= CUT_HEIGHT - RECTANGLE_PADDING - n && j <= CUT_HEIGHT - RECTANGLE_PADDING ))) {

data[i][j] = 2;

}

}

}

}

if (data[i][j] == 1 && j - SLIDER_IMG_OUT_PADDING > 0 && data[i][j - SLIDER_IMG_OUT_PADDING] == 0) {

data[i][j - SLIDER_IMG_OUT_PADDING] = 2;

}

if (data[i][j] == 1 && j + SLIDER_IMG_OUT_PADDING > 0 && j + SLIDER_IMG_OUT_PADDING < CUT_HEIGHT && data[i][j + SLIDER_IMG_OUT_PADDING] == 0) {

data[i][j + SLIDER_IMG_OUT_PADDING] = 2;

}

if (data[i][j] == 1 && i - SLIDER_IMG_OUT_PADDING > 0 && data[i - SLIDER_IMG_OUT_PADDING][j] == 0) {

data[i - SLIDER_IMG_OUT_PADDING][j] = 2;

}

if (data[i][j] == 1 && i + SLIDER_IMG_OUT_PADDING > 0 && i + SLIDER_IMG_OUT_PADDING < CUT_WIDTH && data[i + SLIDER_IMG_OUT_PADDING][j] == 0) {

data[i + SLIDER_IMG_OUT_PADDING][j] = 2;

}

}

}

return data;

}

/**

* 裁剪区块

* 根据生成的滑块形状,对原图和裁剪块进行变色处理

* @param oriImage 原图

* @param targetImage 裁剪图

* @param blockImage 滑块

* @param x 裁剪点x

* @param y 裁剪点y

*/

private static void cutImgByTemplate(BufferedImage oriImage, BufferedImage targetImage, int[][] blockImage, int x, int y) {

for (int i = 0; i < CUT_WIDTH; i++) {

for (int j = 0; j < CUT_HEIGHT; j++) {

int _x = x + i;

int _y = y + j;

int rgbFlg = blockImage[i][j];

int rgb_ori = oriImage.getRGB(_x, _y);

// 原图中对应位置变色处理

if (rgbFlg == 1) {

//抠图上复制对应颜色值

targetImage.setRGB(i,j, rgb_ori);

//原图对应位置颜色变化

oriImage.setRGB(_x, _y, Color.LIGHT_GRAY.getRGB());

} else if (rgbFlg == 2) {

targetImage.setRGB(i, j, Color.WHITE.getRGB());

oriImage.setRGB(_x, _y, Color.GRAY.getRGB());

}else if(rgbFlg == 0){

//int alpha = 0;

targetImage.setRGB(i, j, rgb_ori & 0x00ffffff);

}

}

}

}

/**

* 随机获取一张图片对象

* @param path

* @return

* @throws IOException

*/

public static BufferedImage getRandomImage(String path) throws IOException {

File files = new File(path);

File[] fileList = files.listFiles();

List fileNameList = new ArrayList<>();

if (fileList!=null && fileList.length!=0){

for (File tempFile:fileList){

if (tempFile.isFile() && tempFile.getName().endsWith(".jpg")){

fileNameList.add(tempFile.getAbsolutePath().trim());

}

}

}

Random random = new Random();

File imageFile = new File(fileNameList.get(random.nextInt(fileNameList.size())));

return ImageIO.read(imageFile);

}

/**

* 将IMG输出为文件

* @param image

* @param file

* @throws Exception

*/

public static void writeImg(BufferedImage image, String file) throws Exception {

byte[] imagedata = null;

ByteArrayOutputStream bao=new ByteArrayOutputStream();

ImageIO.write(image,"png",bao);

imagedata = bao.toByteArray();

FileOutputStream out = new FileOutputStream(new File(file));

out.write(imagedata);

out.close();

}

/**

* 将图片转换为BASE64

* @param image

* @return

* @throws IOException

*/

public static String getImageBASE64(BufferedImage image) throws IOException {

ByteArrayOutputStream out = new ByteArrayOutputStream();

ImageIO.write(image,"png",out);

//转成byte数组

byte[] bytes = out.toByteArray();

BASE64Encoder encoder = new BASE64Encoder();

//生成BASE64编码

return encoder.encode(bytes);

}

/**

* 将BASE64字符串转换为图片

* @param base64String

* @return

*/

public static BufferedImage base64StringToImage(String base64String) {

try {

BASE64Decoder decoder=new BASE64Decoder();

byte[] bytes1 = decoder.decodeBuffer(base64String);

ByteArrayInputStream bais = new ByteArrayInputStream(bytes1);

return ImageIO.read(bais);

} catch (IOException e) {

e.printStackTrace();

}

return null;

}

}

代码核心解释

getVerifyImage()接口,主要用来向外部开放,供其他方法调用公开接口,获取最终生成的图片。

其中返回的内容包括:被抠图后处理过的原图,抠图块,扣下图的X轴坐标,Y轴坐标

getBlockData()方法,主要是用来处理抠图块的渲染用色块。因为一般情况下,为了美观、高辨识度以及

对于坐标的隐藏处理,我们会将抠图处理为类似于拼图块的样式,会有凹凸存在。因此需要对其进

行样式处理,并按照处理出来的样式对图片进行上色或者变色处理。具体可研究一下代码

生成的二维数组如图:

image.png

cutImgByTemplate()方法,主要是按照上一个方法生成的二维数组块对原图及抠图进行裁剪和颜色渲染。

处理后的图片如图

image.png

image.png

剩余几个方法基本上都是辅助类,读取图片,将图片转为BASE64等。按需处理即可。

系统流程

在controller层添加一个接口,首先获取图片,以BASE64形式返给前端。

前端获取图片后,获取用户拖动的X轴的距离,连同token及相关信息返回给后端。

后端进行校验,确定是否拖动距离在可允许范围内。并返回给前端相应处理结果。

展示效果:

1.gif

以上仅本人综合网上的教程完成的效果,主要记录下来供自己备忘用。不足之处请见谅。

java验证码图片滑动验证码_图片滑动验证码的生成相关推荐

  1. 前端 验证码隐藏怎么实现_完成图形验证码

    使用 svg-captcha 这个包并结合后端实现图形验证码功能. 基本使用 在我们项目中安装 svg-captcha 包. $ npm install svg-captcha --save 官方文档 ...

  2. python 图片 变清晰_图片无损放大利器,把模糊图片变清晰

    前言 经常下载图片或者使用表情包的朋友都可能会遇到一个问题--图片模糊不清晰! 现有图片分辨率低.图片尺寸小.图片模糊等,很多时候又找不到原始的高分辨率清晰大图,只能将就使用?(ノ-_-)ノ~┻━┻ ...

  3. 转换图片保持画质_图片格式怎么相互转换,如何转换jpg、 bmp、png格式

    图片的格式有很多种,常见的就是jpg.bmp.png格式,这些格式本质上是没有多大的区别,都是安卓和电脑可以直接打开查看的,但是有时候还是会用到一些固定的格式,比如上传个人信息身份的时候,那这个时候如 ...

  4. 10款js图片代码_图片滚动代码_图片切换代码_图片特效代码_图片轮播代码(三)

    jquery banner滑块导航条幻灯片轮播图片滚动 jQuery blockSlide插件熔岩灯标签导航banner焦点图片切换 jquery图片冒泡插件鼠标悬停图片冒泡动画展示 jquery h ...

  5. 10款js图片代码_图片滚动代码_图片切换代码_图片特效代码_图片轮播代码(一)

    实现图片墙时光穿梭特效 swiper图文卡片滑块切换特效 网页放大镜图片预览插件 图片瀑布流tab分类切换特效 js窗帘式图片切换特效 全屏带视频banner轮播图片特效 Swiper仿魅族官网大图轮 ...

  6. 图片太大_图片太大?手把手教你如何用java实现一个高质量图片压缩程序

    使用java几十行代码实现一个高质量图片压缩程序,再也不用去自己找网络的压缩程序啦!而且很多网上的工具还有水印或者其他的限制,自己动手写一个简单的应用,是再合适不过了. 一.实现原理 1.声明两个字符 ...

  7. java斗地主怎么出牌_斗地主滑动选牌出牌(Cocos Creator)

    本文主要讲解以下几个方面: card model 滑动处理 阴影 选择 出牌 Card Model 首先,牌有两个属性:数字.花型: ps:本文现在是,用数字和花型来组成一张牌,有空可以再用另一种形式 ...

  8. java计算图片相似度_图片相似度比较--算法

    最近由于要租房,所以下载了58同城的APP,在找个人房源过程中发现,58同城会把图片相似的发帖纪录被标志出来,并警告用户此信息可能是假的.这里不讨论58同城的这方面做得人性化.而是就图片相似度算法来做 ...

  9. 10款js图片代码_图片滚动代码_图片切换代码_图片特效代码_图片轮播代码(二)

    js电影图片滑动放大展示特效 jQuery列表图片全图滚动预览 jQuery游戏图片手风琴切换代码 响应式图片文字横幅布局代码 swiper新闻大图滚动组合特效 swiper图文标题滚动轮播特效 js ...

  10. 图片出处识别_图片模糊怎么变清晰?方法都在这里了

    我们平时都会与图片打交道,图片资源来源于网络,时常辛辛苦苦找到想要的资源图片,但是清晰度极差,像素太低,图片大小也不满足要求,那这种情况下怎么办呢?搜遍整个网络都没找到,试了以图搜图也没用,那是不是就 ...

最新文章

  1. Kafka-0.10.0.0 集群高可靠实验
  2. 用Java API实现HDFS操作(三)问题汇总
  3. 仔细讨论 C/C++ 字节对齐问题⭐⭐
  4. git的入门摸索和入门研究
  5. linux笔记_20150825_linux下的软件工具唠叨下
  6. 将一副完整的位图均分成n块位图显示
  7. con和com开头单词
  8. HTML 有序列表 字母,HTML之有序列表教程
  9. Idea,webStorm工具栏显示,添加快捷方式建文件
  10. vostro3470装win7_dell latitude3470怎么安装win7系统
  11. MBTI职业性格在软件研发组织中不同岗位的分布研究
  12. SpringBoot 入门
  13. iVX和其它低代码平台没啥好比的 (一)
  14. SpringMVC源码学习
  15. 多处理器下的中断机制
  16. 8月2日Pytorch笔记——梯度、全连接层、GPU加速、Visdom
  17. 利用B2B做外贸需掌握的报价技巧
  18. 最长回文串【python实现】
  19. 蓝牙协议HFP(Hands-Free Profile)电话免提协议 Connection management 连接管理HFP SLC 的建立跟释放
  20. SLO 落地方案:VALET

热门文章

  1. 基于java闲置物品交易系统计算机毕业设计源码+系统+lw文档+mysql数据库+调试部署
  2. println 和print 的区别
  3. java的%d和%f 是什么意思
  4. 如何在 Windows 11 上安装 RSAT 远程服务器管理工​​具
  5. Android从零开始搭建MVVM架构(1),企业级项目实战讲解
  6. c语言void delay是什么意思,51单片机程序解答,void delay (u6i)是什么意思?
  7. CS269I:Incentives in Computer Science 学习笔记 Lecture 12 对称信息和声誉系统
  8. 毕业工作几年后房子我有了,可你还是×××么?
  9. SQL学习:not exists用法
  10. yy直播没声音html,也许有很多话还没说-yy语音没声音我家有两个带麦的耳机,一个说 – 手机爱问...