Java實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼
本文實(shí)例為大家分享了Java實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼的具體代碼,供大家參考,具體內(nèi)容如下
1、后端隨機(jī)生成摳圖和帶有摳圖陰影的背景圖片,后臺保存隨機(jī)摳圖位置坐標(biāo)
2、前端實(shí)現(xiàn)滑動交互,將摳圖拼在摳圖陰影之上,獲取到用戶滑動距離值,比如以下示例
3、前端將用戶滑動距離值傳入后端,后端校驗(yàn)誤差是否在容許范圍內(nèi)。
這里單純校驗(yàn)用戶滑動距離是最基本的校驗(yàn),出于更高的安全考慮,可能還會考慮用戶滑動的整個軌跡,用戶在當(dāng)前頁面的訪問行為等。這些可以很復(fù)雜,甚至借助到用戶行為數(shù)據(jù)分析模型,最終的目標(biāo)都是增加非法的模擬和繞過的難度。這些有機(jī)會可以再歸納總結(jié)常用到的方法,本文重點(diǎn)集中在如何基于Java來一步步實(shí)現(xiàn)滑動驗(yàn)證碼的生成。
可以看到,滑動圖形驗(yàn)證碼,重要有兩個圖片組成,摳塊和帶有摳塊陰影的原圖,這里面有兩個重要特性保證被暴力破解的難度:摳塊的形狀隨機(jī)和摳塊所在原圖的位置隨機(jī)。這樣就可以在有限的圖集中制造出隨機(jī)的、無規(guī)律可尋的摳圖和原圖的配對。
用代碼如何從一張大圖中摳出一個有特定隨機(jī)形狀的小圖呢?
第一步,先確定一個摳出圖的輪廓,方便后續(xù)真正開始執(zhí)行圖片處理操作
圖片是有像素組成,每個像素點(diǎn)對應(yīng)一種顏色,顏色可以用RGB形式表示,外加一個透明度,把一張圖理解成一個平面圖形,左上角為原點(diǎn),向右x軸,向下y軸,一個坐標(biāo)值對應(yīng)該位置像素點(diǎn)的顏色,這樣就可以把一張圖轉(zhuǎn)換成一個二維數(shù)組?;谶@個考慮,輪廓也用二維數(shù)組來表示,輪廓內(nèi)元素值為1,輪廓外元素值對應(yīng)0。
這時候就要想這個輪廓形狀怎么生成了。有坐標(biāo)系、有矩形、有圓形,沒錯,用到數(shù)學(xué)的圖形函數(shù)。典型用到一個圓的函數(shù)方程和矩形的邊線的函數(shù),類似:
(x-a)²+(y-b)²=r²中,有三個參數(shù)a、b、r,即圓心坐標(biāo)為(a,b),半徑r。這些將摳圖放在上文描述的坐標(biāo)系上很容易就圖算出來具體的值。
示例代碼如下:
static int targetWidth = 55;//小圖長 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)識圓心(a,b),半徑為r的圓 //計(jì)算需要的小圖輪廓,用二維數(shù)組來表示,二維數(shù)組有兩張值,0和1,其中0表示沒有顏色,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; }
第二步,有這個輪廓后就可以依據(jù)這個二維數(shù)組的值來判定摳圖并在原圖上摳圖位置處加陰影。
操作如下:
/** * * @Createdate: 2019年1月24日上午10:51:30 * @Title: cutByTemplate * @Description: 有這個輪廓后就可以依據(jù)這個二維數(shù)組的值來判定摳圖并在原圖上摳圖位置處加陰影, * @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]; // 原圖中對應(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)過前面兩步后,就得到了摳圖和帶高斯模糊摳圖陰影的原圖。返回生成的摳圖和帶陰影的大圖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 { //通過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ù)說明 * @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 移動距離 * @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)證過期,請重試"); return new BaseRestResult(resultMap); } if (Math.abs(xWidth - dMoveLength) > 10) { resultMap.put("errcode", 1); resultMap.put("errmsg", "驗(yàn)證不通過"); } else { resultMap.put("errcode", 0); resultMap.put("errmsg", "驗(yàn)證通過"); } } 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="帶摳圖陰影的原圖">
至此后臺java實(shí)現(xiàn)滑塊驗(yàn)證碼關(guān)鍵代碼完成!
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java實(shí)現(xiàn)向數(shù)組里添加元素
這篇文章主要介紹了Java實(shí)現(xiàn)向數(shù)組里添加元素方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11RocketMQ?NameServer架構(gòu)設(shè)計(jì)啟動流程
這篇文章主要為大家介紹了RocketMQ?NameServer架構(gòu)設(shè)計(jì)啟動流程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02在MyBatisPlus中使用@TableField完成字段自動填充的操作
這篇文章主要介紹了在MyBatisPlus中使用@TableField完成字段自動填充的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02