Java實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼
本文實(shí)例為大家分享了Java實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼的具體代碼,供大家參考,具體內(nèi)容如下
1、后端隨機(jī)生成摳圖和帶有摳圖陰影的背景圖片,后臺(tái)保存隨機(jī)摳圖位置坐標(biāo)
2、前端實(shí)現(xiàn)滑動(dòng)交互,將摳圖拼在摳圖陰影之上,獲取到用戶滑動(dòng)距離值,比如以下示例

3、前端將用戶滑動(dòng)距離值傳入后端,后端校驗(yàn)誤差是否在容許范圍內(nèi)。
這里單純校驗(yàn)用戶滑動(dòng)距離是最基本的校驗(yàn),出于更高的安全考慮,可能還會(huì)考慮用戶滑動(dòng)的整個(gè)軌跡,用戶在當(dāng)前頁(yè)面的訪問(wèn)行為等。這些可以很復(fù)雜,甚至借助到用戶行為數(shù)據(jù)分析模型,最終的目標(biāo)都是增加非法的模擬和繞過(guò)的難度。這些有機(jī)會(huì)可以再歸納總結(jié)常用到的方法,本文重點(diǎn)集中在如何基于Java來(lái)一步步實(shí)現(xiàn)滑動(dòng)驗(yàn)證碼的生成。
可以看到,滑動(dòng)圖形驗(yàn)證碼,重要有兩個(gè)圖片組成,摳塊和帶有摳塊陰影的原圖,這里面有兩個(gè)重要特性保證被暴力破解的難度:摳塊的形狀隨機(jī)和摳塊所在原圖的位置隨機(jī)。這樣就可以在有限的圖集中制造出隨機(jī)的、無(wú)規(guī)律可尋的摳圖和原圖的配對(duì)。
用代碼如何從一張大圖中摳出一個(gè)有特定隨機(jī)形狀的小圖呢?
第一步,先確定一個(gè)摳出圖的輪廓,方便后續(xù)真正開(kāi)始執(zhí)行圖片處理操作
圖片是有像素組成,每個(gè)像素點(diǎn)對(duì)應(yīng)一種顏色,顏色可以用RGB形式表示,外加一個(gè)透明度,把一張圖理解成一個(gè)平面圖形,左上角為原點(diǎn),向右x軸,向下y軸,一個(gè)坐標(biāo)值對(duì)應(yīng)該位置像素點(diǎn)的顏色,這樣就可以把一張圖轉(zhuǎn)換成一個(gè)二維數(shù)組?;谶@個(gè)考慮,輪廓也用二維數(shù)組來(lái)表示,輪廓內(nèi)元素值為1,輪廓外元素值對(duì)應(yīng)0。
這時(shí)候就要想這個(gè)輪廓形狀怎么生成了。有坐標(biāo)系、有矩形、有圓形,沒(méi)錯(cuò),用到數(shù)學(xué)的圖形函數(shù)。典型用到一個(gè)圓的函數(shù)方程和矩形的邊線的函數(shù),類似:
(x-a)²+(y-b)²=r²中,有三個(gè)參數(shù)a、b、r,即圓心坐標(biāo)為(a,b),半徑r。這些將摳圖放在上文描述的坐標(biāo)系上很容易就圖算出來(lái)具體的值。
示例代碼如下:
static int targetWidth = 55;//小圖長(zhǎng)
static int targetHeight = 45;//小圖寬
static int circleR = 8;//半徑
static int r1 = 4;//距離點(diǎn)
/**
* @Createdate: 2019年1月24日上午10:52:42
* @Title: getBlockData
* @Description: 生成小圖輪廓
* @author zhoujin
* @return int[][]
* @throws
*/
private static int[][] getBlockData() {
int[][] data = new int[targetWidth][targetHeight];
double x2 = targetWidth -circleR; //47
//隨機(jī)生成圓的位置
double h1 = circleR + Math.random() * (targetWidth-3*circleR-r1);
double po = Math.pow(circleR,2); //64
double xbegin = targetWidth - circleR - r1;
double ybegin = targetHeight- circleR - r1;
//圓的標(biāo)準(zhǔn)方程 (x-a)²+(y-b)²=r²,標(biāo)識(shí)圓心(a,b),半徑為r的圓
//計(jì)算需要的小圖輪廓,用二維數(shù)組來(lái)表示,二維數(shù)組有兩張值,0和1,其中0表示沒(méi)有顏色,1有顏色
for (int i = 0; i < targetWidth; i++) {
for (int j = 0; j < targetHeight; j++) {
double d2 = Math.pow(j - 2,2) + Math.pow(i - h1,2);
double d3 = Math.pow(i - x2,2) + Math.pow(j - h1,2);
if ((j <= ybegin && d2 < po)||(i >= xbegin && d3 > po)) {
data[i][j] = 0;
} else {
data[i][j] = 1;
}
}
}
return data;
}
第二步,有這個(gè)輪廓后就可以依據(jù)這個(gè)二維數(shù)組的值來(lái)判定摳圖并在原圖上摳圖位置處加陰影。
操作如下:
/**
*
* @Createdate: 2019年1月24日上午10:51:30
* @Title: cutByTemplate
* @Description: 有這個(gè)輪廓后就可以依據(jù)這個(gè)二維數(shù)組的值來(lái)判定摳圖并在原圖上摳圖位置處加陰影,
* @author zhoujin
* @param oriImage 原圖
* @param targetImage 摳圖拼圖
* @param templateImage 顏色
* @param x
* @param y void
* @throws
*/
private static void cutByTemplate(BufferedImage oriImage, BufferedImage targetImage, int[][] templateImage, int x, int y){
int[][] martrix = new int[3][3];
int[] values = new int[9];
//創(chuàng)建shape區(qū)域
for (int i = 0; i < targetWidth; i++) {
for (int j = 0; j < targetHeight; j++) {
int rgb = templateImage[i][j];
// 原圖中對(duì)應(yīng)位置變色處理
int rgb_ori = oriImage.getRGB(x + i, y + j);
if (rgb == 1) {
targetImage.setRGB(i, j, rgb_ori);
//摳圖區(qū)域高斯模糊
readPixel(oriImage, x + i, y + j, values);
fillMatrix(martrix, values);
oriImage.setRGB(x + i, y + j, avgMatrix(martrix));
}else{
//這里把背景設(shè)為透明
targetImage.setRGB(i, j, rgb_ori & 0x00ffffff);
}
}
}
}
private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
int xStart = x - 1;
int yStart = y - 1;
int current = 0;
for (int i = xStart; i < 3 + xStart; i++)
for (int j = yStart; j < 3 + yStart; j++) {
int tx = i;
if (tx < 0) {
tx = -tx;
} else if (tx >= img.getWidth()) {
tx = x;
}
int ty = j;
if (ty < 0) {
ty = -ty;
} else if (ty >= img.getHeight()) {
ty = y;
}
pixels[current++] = img.getRGB(tx, ty);
}
}
private static void fillMatrix(int[][] matrix, int[] values) {
int filled = 0;
for (int i = 0; i < matrix.length; i++) {
int[] x = matrix[i];
for (int j = 0; j < x.length; j++) {
x[j] = values[filled++];
}
}
}
private static int avgMatrix(int[][] matrix) {
int r = 0;
int g = 0;
int b = 0;
for (int i = 0; i < matrix.length; i++) {
int[] x = matrix[i];
for (int j = 0; j < x.length; j++) {
if (j == 1) {
continue;
}
Color c = new Color(x[j]);
r += c.getRed();
g += c.getGreen();
b += c.getBlue();
}
}
return new Color(r / 8, g / 8, b / 8).getRGB();
}
經(jīng)過(guò)前面兩步后,就得到了摳圖和帶高斯模糊摳圖陰影的原圖。返回生成的摳圖和帶陰影的大圖base64碼及摳圖坐標(biāo)。
/**
* @Description: 讀取本地圖片,生成拼圖驗(yàn)證碼
* @author zhoujin
* @return Map<String,Object> 返回生成的摳圖和帶摳圖陰影的大圖 base64碼及摳圖坐標(biāo)
*/
public static Map<String,Object> createImage(File file, Map<String,Object> resultMap){
try {
BufferedImage oriImage = ImageIO.read(file);
Random random = new Random();
//X軸距離右端targetWidth Y軸距離底部targetHeight以上
int widthRandom = random.nextInt(oriImage.getWidth()- 2*targetWidth) + targetWidth;
int heightRandom = random.nextInt(oriImage.getHeight()- targetHeight);
logger.info("原圖大小{} x {},隨機(jī)生成的坐標(biāo) X,Y 為({},{})",oriImage.getWidth(),oriImage.getHeight(),widthRandom,heightRandom);
BufferedImage targetImage= new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_4BYTE_ABGR);
cutByTemplate(oriImage,targetImage,getBlockData(),widthRandom,heightRandom);
resultMap.put("bigImage", getImageBASE64(oriImage));//大圖
resultMap.put("smallImage", getImageBASE64(targetImage));//小圖
resultMap.put("xWidth",widthRandom);
resultMap.put("yHeight",heightRandom);
} catch (Exception e) {
logger.info("創(chuàng)建圖形驗(yàn)證碼異常",e);
} finally{
return resultMap;
}
}
/**
* @Description: 讀取網(wǎng)絡(luò)圖片,生成拼圖驗(yàn)證碼
* @author zhoujin
* @return Map<String,Object> 返回生成的摳圖和帶摳圖陰影的大圖 base64碼及摳圖坐標(biāo)
*/
public static Map<String,Object> createImage(String imgUrl, Map<String,Object> resultMap){
try {
//通過(guò)URL 讀取圖片
URL url = new URL(imgUrl);
BufferedImage bufferedImage = ImageIO.read(url.openStream());
Random rand = new Random();
int widthRandom = rand.nextInt(bufferedImage.getWidth()- targetWidth - 100 + 1 ) + 100;
int heightRandom = rand.nextInt(bufferedImage.getHeight()- targetHeight + 1 );
logger.info("原圖大小{} x {},隨機(jī)生成的坐標(biāo) X,Y 為({},{})",bufferedImage.getWidth(),bufferedImage.getHeight(),widthRandom,heightRandom);
BufferedImage target= new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_4BYTE_ABGR);
cutByTemplate(bufferedImage,target,getBlockData(),widthRandom,heightRandom);
resultMap.put("bigImage", getImageBASE64(bufferedImage));//大圖
resultMap.put("smallImage", getImageBASE64(target));//小圖
resultMap.put("xWidth",widthRandom);
resultMap.put("yHeight",heightRandom);
} catch (Exception e) {
logger.info("創(chuàng)建圖形驗(yàn)證碼異常",e);
} finally{
return resultMap;
}
}
/**
* @Title: getImageBASE64
* @Description: 圖片轉(zhuǎn)BASE64
* @author zhoujin
* @param image
* @return
* @throws IOException String
*/
public static String getImageBASE64(BufferedImage image) throws IOException {
byte[] imagedata = null;
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ImageIO.write(image,"png",bao);
imagedata=bao.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
String BASE64IMAGE=encoder.encodeBuffer(imagedata).trim();
BASE64IMAGE = BASE64IMAGE.replaceAll("\r|\n", ""); //刪除 \r\n
return BASE64IMAGE;
}
控制層代碼實(shí)現(xiàn)及校驗(yàn)驗(yàn)證碼:
/**
* @param @return 參數(shù)說(shuō)明
* @return BaseRestResult 返回類型
* @Description: 生成滑塊拼圖驗(yàn)證碼
*/
@RequestMapping(value = "/getImageVerifyCode.do", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
public BaseRestResult getImageVerifyCode() {
Map<String, Object> resultMap = new HashMap<>();
//讀取本地路徑下的圖片,隨機(jī)選一條
File file = new File(this.getClass().getResource("/image").getPath());
File[] files = file.listFiles();
int n = new Random().nextInt(files.length);
File imageUrl = files[n];
ImageUtil.createImage(imageUrl, resultMap);
//讀取網(wǎng)絡(luò)圖片
//ImageUtil.createImage("/7986d66f29bfeb6015aaaec33d33fcd1d875ca16316f-2bMHNG_fw658",resultMap);
session.setAttribute("xWidth", resultMap.get("xWidth"));
resultMap.remove("xWidth");
resultMap.put("errcode", 0);
resultMap.put("errmsg", "success");
return new BaseRestResult(resultMap);
}
/**
* 校驗(yàn)滑塊拼圖驗(yàn)證碼
*
* @param moveLength 移動(dòng)距離
* @return BaseRestResult 返回類型
* @Description: 生成圖形驗(yàn)證碼
*/
@RequestMapping(value = "/verifyImageCode.do", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
public BaseRestResult verifyImageCode(@RequestParam(value = "moveLength") String moveLength) {
Double dMoveLength = Double.valueOf(moveLength);
Map<String, Object> resultMap = new HashMap<>();
try {
Integer xWidth = (Integer) session.getAttribute("xWidth");
if (xWidth == null) {
resultMap.put("errcode", 1);
resultMap.put("errmsg", "驗(yàn)證過(guò)期,請(qǐng)重試");
return new BaseRestResult(resultMap);
}
if (Math.abs(xWidth - dMoveLength) > 10) {
resultMap.put("errcode", 1);
resultMap.put("errmsg", "驗(yàn)證不通過(guò)");
} else {
resultMap.put("errcode", 0);
resultMap.put("errmsg", "驗(yàn)證通過(guò)");
}
} catch (Exception e) {
throw new EsServiceException(e.getMessage());
} finally {
session.removeAttribute("xWidth");
}
return new BaseRestResult(resultMap);
}
前端顯示圖片代碼:
<img src="data:image/png;base64,+返回的base64圖片碼" alt="摳圖"> <img src="data:image/png;base64,+返回的base64圖片碼" alt="帶摳圖陰影的原圖">
至此后臺(tái)java實(shí)現(xiàn)滑塊驗(yàn)證碼關(guān)鍵代碼完成!
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis一次執(zhí)行多條SQL語(yǔ)句的操作
這篇文章主要介紹了MyBatis一次執(zhí)行多條SQL語(yǔ)句的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
Java實(shí)現(xiàn)向數(shù)組里添加元素
這篇文章主要介紹了Java實(shí)現(xiàn)向數(shù)組里添加元素方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
RocketMQ?NameServer架構(gòu)設(shè)計(jì)啟動(dòng)流程
這篇文章主要為大家介紹了RocketMQ?NameServer架構(gòu)設(shè)計(jì)啟動(dòng)流程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
在MyBatisPlus中使用@TableField完成字段自動(dòng)填充的操作
這篇文章主要介紹了在MyBatisPlus中使用@TableField完成字段自動(dòng)填充的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02

