前段时间上级要我弄一个登录时候的图片拖动验证。类似于哔哩哔哩登录的那种。在网上找了一波,发现有的要钱,有的是直接前台的拖动验证操作的。但是不清楚到底是在前端验证还是后端验证安全点。想来想去我觉得得在后端,原谅我本来是后端的被赶鸭子上架写前端潜意识觉得后端更安全。现在记录一下。
日常不变的SSM框架。
思路:后端图片裁剪转为base64(裁剪的X轴起点存在session,本来是想存redis)-->前端展示(获取前端拖动的距离ajax进后台与session中X轴对比)
就在两步。
首先是图片裁剪类(在网上找的一位大佬的自己改了一下):
package com.image.yanzhen;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import sun.misc.BASE64Encoder;
public class ImageCut {
/**
* 源图片路径名称如:c:\1.jpg
*/
private String srcpath = "e:/poool.jpg";
/**
* 剪切图片存放路径名称.如:c:\2.jpg
*/
private String subpath = "e:/pool_end";
/**
* jpg图片格式
*/
private static final String IMAGE_FORM_OF_JPG = "jpg";
/**
* png图片格式
*/
private static final String IMAGE_FORM_OF_PNG = "png";
/**
* 剪切点x坐标
*/
private int x;
/**
* 剪切点y坐标
*/
private int y;
/**
* 剪切点宽度
*/
private int width;
/**
* 剪切点高度
*/
private int height;
public ImageCut() {
}
public ImageCut(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public static void main(String[] args) throws Exception {
ImageCut imageCut = new ImageCut(134, 0, 366, 366);
imageCut.cut(imageCut.getSrcpath(), imageCut.getSubpath());
}
/**
* 返回包含所有当前已注册 ImageReader 的 Iterator,这些 ImageReader 声称能够解码指定格式。
* 参数:formatName - 包含非正式格式名称 .(例如 "jpeg" 或 "tiff")等 。
*
* @param postFix
* 文件的后缀名
* @return
*/
public Iterator<ImageReader> getImageReadersByFormatName(String postFix) {
switch (postFix) {
case IMAGE_FORM_OF_JPG:
return ImageIO.getImageReadersByFormatName(IMAGE_FORM_OF_JPG);
case IMAGE_FORM_OF_PNG:
return ImageIO.getImageReadersByFormatName(IMAGE_FORM_OF_PNG);
default:
return ImageIO.getImageReadersByFormatName(IMAGE_FORM_OF_JPG);
}
}
/**
* 对图片裁剪,并把裁剪完蛋新图片保存 。
* @param srcpath 源图片路径
* @param subpath 剪切图片存放路径
* @throws IOException
*/
public String cut(String srcpath, String subpath) throws IOException {
FileInputStream is = null;
ImageInputStream iis = null;
try {
// 读取图片文件
is = new FileInputStream(srcpath);
// 获取文件的后缀名
String postFix = getPostfix(srcpath);
System.out.println("图片格式为:" + postFix);
/*
* 返回包含所有当前已注册 ImageReader 的 Iterator,这些 ImageReader 声称能够解码指定格式。
* 参数:formatName - 包含非正式格式名称 .(例如 "jpeg" 或 "tiff")等 。
*/
Iterator<ImageReader> it = getImageReadersByFormatName(postFix);
ImageReader reader = it.next();
// 获取图片流
iis = ImageIO.createImageInputStream(is);
/*
* <p>iis:读取源.true:只向前搜索 </p>.将它标记为 ‘只向前搜索’。
* 此设置意味着包含在输入源中的图像将只按顺序读取,可能允许 reader 避免缓存包含与以前已经读取的图像关联的数据的那些输入部分。
*/
reader.setInput(iis, true);
/*
* <p>描述如何对流进行解码的类<p>.用于指定如何在输入时从 Java Image I/O
* 框架的上下文中的流转换一幅图像或一组图像。用于特定图像格式的插件 将从其 ImageReader 实现的
* getDefaultReadParam 方法中返回 ImageReadParam 的实例。
*/
ImageReadParam param = reader.getDefaultReadParam();
/*
* 图片裁剪区域。Rectangle 指定了坐标空间中的一个区域,通过 Rectangle 对象
* 的左上顶点的坐标(x,y)、宽度和高度可以定义这个区域。
*/
Rectangle rect = new Rectangle(x, y, width, height);
// 提供一个 BufferedImage,将其用作解码像素数据的目标。
param.setSourceRegion(rect);
/*
* 使用所提供的 ImageReadParam 读取通过索引 imageIndex 指定的对象,并将 它作为一个完整的
* BufferedImage 返回。
*/
BufferedImage bi = reader.read(0, param);
ByteArrayOutputStream CatBaos = new ByteArrayOutputStream();//io流
ImageIO.write(bi, "jpg", CatBaos);//写入流中
byte[] CutBytes = CatBaos.toByteArray();//转换成字节
BASE64Encoder encoder = new BASE64Encoder();
String CutPng_base64 = encoder.encodeBuffer(CutBytes).trim();//转换成base64串
CutPng_base64 = CutPng_base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n
// String path = subpath + "_" + new Date().getTime() + "." + postFix;
//
// File file = new File(path);
// File fileParent = file.getParentFile();
// if(!fileParent.exists()){
// System.out.println("自动创建文件");
// fileParent.mkdirs();
// }
// file.createNewFile();
//
// // 保存新图片
// ImageIO.write(bi, postFix, new File(path));
return CutPng_base64;
} finally {
if (is != null)
is.close();
if (iis != null)
iis.close();
}
}
/**
* 获取inputFilePath的后缀名,如:"e:/test.pptx"的后缀名为:"pptx"<br>
*
* @param inputFilePath
* @return
*/
public String getPostfix(String inputFilePath) {
return inputFilePath.substring(inputFilePath.lastIndexOf(".") + 1);
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getSrcpath() {
return srcpath;
}
public void setSrcpath(String srcpath) {
this.srcpath = srcpath;
}
public String getSubpath() {
return subpath;
}
public void setSubpath(String subpath) {
this.subpath = subpath;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
再下来就是裁剪了(代码辣鸡,大家看看就好)
package com.image.yanzhen;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.imageio.ImageIO;
import com.attendance.utils.PathUtil;
import sun.misc.BASE64Encoder;
public class ImgCutTest {
public Map<Object,Object> getBase() throws FileNotFoundException, IOException {
Map<Object,Object> map = new HashMap<Object,Object>();
Random rand = new Random();
int ImgIndex = rand.nextInt(6); //生成0-6以内的随机数
if(ImgIndex==0){
ImgIndex = 1;
}
String path = PathUtil.getClasspath()+"baseImg/"+ImgIndex+".jpg";
System.out.println("图片路径-->"+path);
File picture = new File(path);
BufferedImage sourceImg = ImageIO.read(new FileInputStream(picture));
/*************************裁剪图片获得base64*************************/
int CJX = rand.nextInt(700); //生成0-700以内的随机数
int CJY = rand.nextInt(480); //生成0-480以内的随机数
System.out.println("随机x起点-->"+CJX);
System.out.println("随机y起点-->"+CJY);
if(CJX<200){
System.out.println("太少了");
CJX = CJX + 200;
System.out.println("增加后-->"+CJX);
}
int CutX1 = CJX; //裁剪X轴起点
int CutY1 = CJY; //裁剪Y轴起点
int CutW1 = 200; //裁剪宽度
int CutH1 = 120; //裁剪高度
System.out.println(CutX1+"、"+CutY1+"、"+CutW1+"、"+CutH1);
ImageCut imageCut1 = new ImageCut(CutX1, CutY1, CutW1, CutH1);
String CutPng_base64 = imageCut1.cut(path, null);
/********************************************************/
/*************************生成数组*************************/
int[][] data = new int[sourceImg.getWidth()][sourceImg.getHeight()];
for (int i=0;i<sourceImg.getWidth();i++){//1280
for(int j=0;j<sourceImg.getHeight();j++){//720
if(i<CJX+200&&i>=CJX&&j<CJY+120&&j>CJY){
data[i][j]=1;
}else {
data[i][j]=0;
}
}
}
/*********************************************************/
/************************图片局部变黑************************/
for (int i = 0; i < sourceImg.getWidth(); i++) {
for (int j = 0; j < sourceImg.getHeight(); j++) {
int rgb = data[i][j];
// 原图中对应位置变色处理
int rgb_ori = sourceImg.getRGB(i, j);
if (rgb == 1) {
//颜色处理
int r = (0XFF000000 & rgb_ori);
int g = (0XFF000000 & (rgb_ori >> 8));
int b = (0XFF000000 & (rgb_ori >> 16));
int Gray = (r*2 + g*5 + b*1) >> 3;
//原图对应位置颜色变化
sourceImg.setRGB( i, j, Gray);
}
}
}
/**********************************************************/
/************************阴影图片转base64************************/
BASE64Encoder encoder = new BASE64Encoder();
ByteArrayOutputStream YYbaos = new ByteArrayOutputStream();//io流
ImageIO.write(sourceImg, "jpg", YYbaos);//写入流中
byte[] bytes = YYbaos.toByteArray();//转换成字节
String YYPng_base64 = encoder.encodeBuffer(bytes).trim();//转换成base64串
YYPng_base64 = YYPng_base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n
/**********************************************************/
map.put("CJX", CJX); //裁剪开始X坐标
map.put("CJY", CJY); //裁剪开始Y坐标
map.put("YYPng_base64", YYPng_base64); //阴影图片base
map.put("CutPng_base64", CutPng_base64);//裁剪图片base
return map;
}
}
最重要的就是上面
这个两个了。
然后是controller调用图片裁剪并返回前台
/**
* 去图片验证页面
* @param session
* @return
* @throws IOException
*/
@RequestMapping(value="/GoUploadImg.do",method = RequestMethod.GET)
@ResponseBody
public Object IndexGoLogin(HttpSession session) throws IOException{
System.out.println("进入图片上传页面");
Map<Object,Object> map = new HashMap<Object,Object>();
map = new ImgCutTest().getBase();
String uuid = UuidUtil.get32UUID();
//前台图片展示为原图的一半
int CJX = (int) map.get("CJX")/2;
session.setAttribute(uuid, CJX);
map.put("uuid", uuid);
return JSONArray.toJSONString(map);
}
接下来就是前台的一些展示,和拖动的一些东西。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<script type="text/javascript" charset="utf-8" src="statics/jquery-3.2.1.min.js"></script>
<link rel="stylesheet" href="statics/layui/css/layui.css" type="text/css"/>
<script type="text/javascript" src="statics/layui/layui.js"></script>
<script>
function getYZ(){
$.ajax({
type:"GET",
url:"GoUploadImg.do",
data:{},
dataType:"json",
success:function(data){
console.log(data);
$("#YZUUID").val(data.uuid);
$("#JQX").html(data.CJX);
$("#JQY").html(data.CJY);
$("#JQIMG").html("<img src=\"data:image/jpeg;base64,"+data.CutPng_base64+"\"/>");
$("#YYIMG").html("<img src=\"data:image/jpeg;base64,"+data.YYPng_base64+"\"/>");
$("#YZDIVIMG").html("<img style=\"max-width:100%;max-height:100%;\" src=\"data:image/jpeg;base64,"+data.YYPng_base64+"\"/>");
$("#CJDIVIMG").html("<img style=\"max-width:100%;max-height:100%;\" src=\"data:image/jpeg;base64,"+data.CutPng_base64+"\"/>");
//移动上下位置
var MarTop = data.CJY/2+20+"px";
$("#CJDIVIMG").css('margin-top',MarTop);
},error:function(data){//当访问是,404,500,等非200错误状态码
alert("亲的网络突然出错了呢!请稍后刷新再操作!");
}
});
}
$(document).ready(function(){
var w = 450;
var PL_Size = 100; //缺失拼图的大小
var padding = 0; //缺失拼图与边框的距离
// 滑块拖动
var moveStart = '';//定义一个鼠标按下的X轴值
//鼠标按下
$(".slider-btn").mousedown(function(e){
e = e || window.event;
// 鼠标在滑块按下切换滑块背景
$(this).css({
"background-position":"0 -216px"
});
moveStart = e.pageX;//记录鼠标按下时的坐标 X轴值
});
//鼠标拖动(这里使用全局监听鼠标移动的事件)
onmousemove = function(e) {
e = e || window.event;
var moveX = e.pageX;//监听鼠标的位置
var d = moveX-moveStart; //鼠标按住后在X轴上移动的距离
if(moveStart == '') {
// console.log('未拖动滑块');
} else {
if(d<0 || d>(w-padding-PL_Size)) {
// console.log('超过范围');
} else {
var OtherD = d+20;
$(".slider-btn").css({
"left":d + 'px',
"transition":"inherit"
});
$("#CJDIVIMG").css({
"left":OtherD + 'px',
"transition":"inherit"
});
}
}
};
//鼠标松开 (这里使用全局监听鼠标松开的事件)
onmouseup = function (e) {
e = e || window.event;
var moveEnd_X = e.pageX - moveStart;//松开鼠标后滑块移动的距离
if(moveStart == '') {
} else {
var uuid = $("#YZUUID").val();
$.ajax({
type:"POST",
url:"YanZhenX.do",
data:{uuid:uuid,moveEnd_X:moveEnd_X},
dataType:"json",
success:function(data){
console.log(data);
if(data.YZ=="yes"){
$("#YZDExpress").html("<i class=\"layui-icon layui-icon-ok-circle\" style=\"font-size: 18px; color: green;\"><i style=\"font-size:15px;\">验证通过 </i></i><font style=\"font-size:15px;\">你的速度飞快,超过绝大多数人</font>");
}else{
$("#YZDExpress").html("<i class=\"layui-icon layui-icon-close-fill\" style=\"font-size: 18px; color: red;\"><i style=\"font-size:15px;\">验证失败 </i></i><font style=\"font-size:15px;\">拖动滑块将悬浮图像正确拼接</font>");
}
},error:function(data){//当访问是,404,500,等非200错误状态码
alert("亲的网络突然出错了呢!请稍后刷新再操作!");
}
});
}
setTimeout(function () {
$(".slider-btn").css({
"left":'0',
"transition":"left 0.5s"
});
$("#CJDIVIMG").css({
"left":'20px',
"transition":"left 0.5s"
});
$("#YZDExpress").html("");
},1000);
$(".slider-btn").css({
"background-position":"0 -84px"
});
moveStart = '';// 清空上一次鼠标按下时的坐标X轴值;
}
});
</script>
<style>
.YZDIV{
width: 490px;
height:360px;
border: solid #E7E3DA thin;
background-color: #F4ECE3;
border-radius: 20px;
text-align: center;
}
.YYIMG{
width: 450px;
height:300px;
border: none;
background-color: #ECE4DD;
margin:0 auto;
margin-top:20px;
}
.CJIMG{
width: 100px;
height:60px;
border: #D3D664 solid thin;
background-color: #ECE4DD;
z-index: 200;
position: absolute;
left:20px;
}
.slider-btn {
position:absolute;
width:44px;
height:44px;
left:0;
top:-7px;
z-index:12;
cursor:pointer;
background-image:url("statics/image/sprite.3.2.0.png");
background-position:0 -84px;
transition:inherit;
}
.layui-icon-refresh-3:HOVER {
color: green;
}
</style>
<body onload="getYZ()">
<!-- <button onclick="getYZ()">验证素材</button> -->
<input type="hidden" id="YZUUID"/>
<div class="YZDIV">
<div class="CJIMG" id="CJDIVIMG"></div>
<div class="YYIMG" id="YZDIVIMG"></div>
<i onclick="getYZ()" class="layui-icon layui-icon-refresh-3" style="font-size: 18px;float: left;line-height: 40px;margin-left: 12px;cursor: pointer;" title="刷新验证"></i>
<i id="YZDExpress" style="line-height: 40px;"></i>
</div>
<br/>
<div style="position:relative;width:490px;">
<div style="border:1px solid #c3c3c3;border-radius:24px;background:#ece4dd;box-shadow:0 1px 1px rgba(12,10,10,0.2) inset;">
<p style="-moz-user-select: none; -khtml-user-select: none; user-select: none;font-size:12px;color: #486c80;line-height:28px;margin:0;text-align:right;padding-right:22px;text-align: center;">按住左边滑块,拖动完成上方拼图</p>
</div>
<div class="slider-btn" id="ANNIU"></div>
</div>
<br/><br/><br/><br/>
截取X起点:<div id="JQX"></div><br/>
截取Y起点:<div id="JQY"></div><br/>
截取图片:<div id="JQIMG"></div><br/>
阴影图片:<div id="YYIMG"></div><br/>
</body>
</html>
这边拖动在鼠标松开的时候会回后台进行验证
/**
* 滑动验证
* @param uuid 标识符
* @param moveEnd_X 滑动距离
* @param session
* @return
* @throws IOException
*/
@RequestMapping(value="/YanZhenX.do",method = RequestMethod.POST)
@ResponseBody
public Object YanZhenX(@RequestParam String uuid,@RequestParam int moveEnd_X,HttpSession session) throws IOException{
System.out.println("进行验证");
Map<Object,Object> map = new HashMap<Object,Object>();
int CJX = (int) session.getAttribute(uuid);
System.out.println("uuid-->"+uuid);
System.out.println("滑动x距离-->"+moveEnd_X);
System.out.println("裁剪距离-->"+CJX);
if(moveEnd_X>CJX-3&&moveEnd_X<CJX+3){ //偏差在3之类
System.out.println("拼接成功");
map.put("YZ", "yes");
}else{
System.out.println("偏差过大");
map.put("YZ", "no");
}
return JSONArray.toJSONString(map);
}
大致的就这么个样子。
放几张效果图
好了,就这些了。有问题请多多指点。对了那些图片我都是直接选的900*600的
源码也放一下下。大家想看的可以下载,虽然基本的代码都在上面了
https://download.youkuaiyun.com/download/qq_38196854/10816634