Spring框架實(shí)現(xiàn)滑動(dòng)驗(yàn)證碼功能的代碼示例
1. 整體描述
之前項(xiàng)目需要在驗(yàn)證碼模塊,增加滑動(dòng)驗(yàn)證碼,用來給手機(jī)端使用的,大概看了下,主要方法就是將圖片切割,然后記住偏移量,進(jìn)行滑動(dòng),前端驗(yàn)證的時(shí)候,需要用前端傳入的偏移量和生成的偏移量進(jìn)行對比,如果在閾值之內(nèi),就驗(yàn)證通過,否則就不通過。具體實(shí)現(xiàn)方式見下文。之前沒時(shí)間寫,最近記錄一下。
2. 具體實(shí)現(xiàn)
本工程主要依賴springboot框架,并且需要redis存驗(yàn)證碼的信息,還需要幾個(gè)圖片,用來生成驗(yàn)證碼。
2.1 滑動(dòng)驗(yàn)證碼實(shí)體類
package com.thcb.captchademo.captcha.domain; import lombok.Data; /** * 滑動(dòng)驗(yàn)證碼 * * @author thcb * @date 2023-05-25 */ @Data public class Captcha { /** * 隨機(jī)字符串 **/ private String nonceStr; /** * 驗(yàn)證值 **/ private String value; /** * 生成的畫布的base64 **/ private String canvasSrc; /** * 畫布寬度 **/ private Integer canvasWidth; /** * 畫布高度 **/ private Integer canvasHeight; /** * 生成的阻塞塊的base64 **/ private String blockSrc; /** * 阻塞塊寬度 **/ private Integer blockWidth; /** * 阻塞塊高度 **/ private Integer blockHeight; /** * 阻塞塊凸凹半徑 **/ private Integer blockRadius; /** * 阻塞塊的橫軸坐標(biāo) **/ private Integer blockX; /** * 阻塞塊的縱軸坐標(biāo) **/ private Integer blockY; /** * 圖片獲取位置 **/ private Integer place; }
2.2 滑動(dòng)驗(yàn)證碼登錄VO
package com.thcb.captchademo.captcha.domain; import lombok.Data; /** * 滑動(dòng)驗(yàn)證碼登錄Vo * * @author thcb * @date 2023-05-25 */ @Data public class LoginVo { /** * 隨機(jī)字符串 **/ private String nonceStr; /** * 驗(yàn)證值 **/ private String value; }
2.3 滑動(dòng)驗(yàn)證碼接口返回類
package com.thcb.captchademo.captcha.utils; import java.util.HashMap; /** * 操作消息提醒 * * @author thcb */ public class AjaxResult extends HashMap<String, Object> { private static final long serialVersionUID = 1L; /** * 狀態(tài)碼 */ public static final String CODE_TAG = "code"; /** * 返回內(nèi)容 */ public static final String MSG_TAG = "msg"; /** * 數(shù)據(jù)對象 */ public static final String DATA_TAG = "data"; /** * 初始化一個(gè)新創(chuàng)建的 AjaxResult 對象,使其表示一個(gè)空消息。 */ public AjaxResult() { } /** * 初始化一個(gè)新創(chuàng)建的 AjaxResult 對象 * * @param code 狀態(tài)碼 * @param msg 返回內(nèi)容 */ public AjaxResult(int code, String msg) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); } /** * 初始化一個(gè)新創(chuàng)建的 AjaxResult 對象 * * @param code 狀態(tài)碼 * @param msg 返回內(nèi)容 * @param data 數(shù)據(jù)對象 */ public AjaxResult(int code, String msg, Object data) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); if (data != null && !data.equals("")) { super.put(DATA_TAG, data); } } /** * 返回成功消息 * * @return 成功消息 */ public static AjaxResult success() { return AjaxResult.success("操作成功"); } /** * 返回成功數(shù)據(jù) * * @return 成功消息 */ public static AjaxResult success(Object data) { return AjaxResult.success("操作成功", data); } /** * 返回成功消息 * * @param msg 返回內(nèi)容 * @return 成功消息 */ public static AjaxResult success(String msg) { return AjaxResult.success(msg, null); } /** * 返回成功消息 * * @param msg 返回內(nèi)容 * @param data 數(shù)據(jù)對象 * @return 成功消息 */ public static AjaxResult success(String msg, Object data) { return new AjaxResult(200, msg, data); } /** * 返回錯(cuò)誤消息 * * @return */ public static AjaxResult error() { return AjaxResult.error("操作失敗"); } /** * 返回錯(cuò)誤消息 * * @param msg 返回內(nèi)容 * @return 警告消息 */ public static AjaxResult error(String msg) { return AjaxResult.error(msg, null); } /** * 返回錯(cuò)誤消息 * * @param msg 返回內(nèi)容 * @param data 數(shù)據(jù)對象 * @return 警告消息 */ public static AjaxResult error(String msg, Object data) { return new AjaxResult(500, msg, data); } /** * 返回錯(cuò)誤消息 * * @param code 狀態(tài)碼 * @param msg 返回內(nèi)容 * @return 警告消息 */ public static AjaxResult error(int code, String msg) { return new AjaxResult(code, msg, null); } }
2.4 滑動(dòng)驗(yàn)證碼工具類
此類是核心類,主要實(shí)現(xiàn)了圖片的切割功能。
package com.thcb.captchademo.captcha.utils; import com.thcb.captchademo.captcha.domain.Captcha; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.Base64; import java.util.Objects; import java.util.Random; /** * 滑動(dòng)驗(yàn)證碼工具類 * * @author thcb * @date 2023-05-25 */ public class CaptchaUtils { /** * 網(wǎng)絡(luò)圖片地址 **/ private final static String IMG_URL = "https://loyer.wang/view/ftp/wallpaper/%s.jpg"; /** * 本地圖片地址 **/ private final static String IMG_PATH = "E:\\caphcha\\%s.jpg"; /** * 入?yún)⑿r?yàn)設(shè)置默認(rèn)值 **/ public static void checkCaptcha(Captcha captcha) { //設(shè)置畫布寬度默認(rèn)值 if (captcha.getCanvasWidth() == null) { captcha.setCanvasWidth(320); } //設(shè)置畫布高度默認(rèn)值 if (captcha.getCanvasHeight() == null) { captcha.setCanvasHeight(155); } //設(shè)置阻塞塊寬度默認(rèn)值 if (captcha.getBlockWidth() == null) { captcha.setBlockWidth(65); } //設(shè)置阻塞塊高度默認(rèn)值 if (captcha.getBlockHeight() == null) { captcha.setBlockHeight(55); } //設(shè)置阻塞塊凹凸半徑默認(rèn)值 if (captcha.getBlockRadius() == null) { captcha.setBlockRadius(9); } //設(shè)置圖片來源默認(rèn)值 if (captcha.getPlace() == null) { captcha.setPlace(1); } } /** * 獲取指定范圍內(nèi)的隨機(jī)數(shù) **/ public static int getNonceByRange(int start, int end) { Random random = new Random(); return random.nextInt(end - start + 1) + start; } /** * 獲取驗(yàn)證碼資源圖 **/ public static BufferedImage getBufferedImage(Integer place) { try { //隨機(jī)圖片 //獲取網(wǎng)絡(luò)資源圖片 if (0 == place) { int nonce = getNonceByRange(0, 1000); String imgUrl = String.format(IMG_URL, nonce); URL url = new URL(imgUrl); return ImageIO.read(url.openStream()); } //獲取本地圖片 else { int nonce = getNonceByRange(0, 20); String imgPath = String.format(IMG_PATH, nonce); File file = new File(imgPath); return ImageIO.read(file); } } catch (Exception e) { System.out.println("獲取拼圖資源失敗"); //異常處理 return null; } } /** * 調(diào)整圖片大小 **/ public static BufferedImage imageResize(BufferedImage bufferedImage, int width, int height) { Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH); BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics2D = resultImage.createGraphics(); graphics2D.drawImage(image, 0, 0, null); graphics2D.dispose(); return resultImage; } /** * 摳圖,并生成阻塞塊 **/ public static void cutByTemplate(BufferedImage canvasImage, BufferedImage blockImage, int blockWidth, int blockHeight, int blockRadius, int blockX, int blockY) { BufferedImage waterImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR); //阻塞塊的輪廓圖 int[][] blockData = getBlockData(blockWidth, blockHeight, blockRadius); //創(chuàng)建阻塞塊具體形狀 for (int i = 0; i < blockWidth; i++) { for (int j = 0; j < blockHeight; j++) { try { //原圖中對應(yīng)位置變色處理 if (blockData[i][j] == 1) { //背景設(shè)置為黑色 waterImage.setRGB(i, j, Color.BLACK.getRGB()); blockImage.setRGB(i, j, canvasImage.getRGB(blockX + i, blockY + j)); //輪廓設(shè)置為白色,取帶像素和無像素的界點(diǎn),判斷該點(diǎn)是不是臨界輪廓點(diǎn) if (blockData[i + 1][j] == 0 || blockData[i][j + 1] == 0 || blockData[i - 1][j] == 0 || blockData[i][j - 1] == 0) { blockImage.setRGB(i, j, Color.WHITE.getRGB()); waterImage.setRGB(i, j, Color.WHITE.getRGB()); } } //這里把背景設(shè)為透明 else { blockImage.setRGB(i, j, Color.TRANSLUCENT); waterImage.setRGB(i, j, Color.TRANSLUCENT); } } catch (ArrayIndexOutOfBoundsException e) { //防止數(shù)組下標(biāo)越界異常 } } } //在畫布上添加阻塞塊水印 addBlockWatermark(canvasImage, waterImage, blockX, blockY); } /** * 構(gòu)建拼圖輪廓軌跡 **/ private static int[][] getBlockData(int blockWidth, int blockHeight, int blockRadius) { int[][] data = new int[blockWidth][blockHeight]; double po = Math.pow(blockRadius, 2); //隨機(jī)生成兩個(gè)圓的坐標(biāo),在4個(gè)方向上 隨機(jī)找到2個(gè)方向添加凸/凹 //凸/凹1 Random random1 = new Random(); int face1 = random1.nextInt(4); //凸/凹2 int face2; //保證兩個(gè)凸/凹不在同一位置 do { Random random2 = new Random(); face2 = random2.nextInt(4); } while (face1 == face2); //獲取凸/凹起位置坐標(biāo) int[] circle1 = getCircleCoords(face1, blockWidth, blockHeight, blockRadius); int[] circle2 = getCircleCoords(face2, blockWidth, blockHeight, blockRadius); //隨機(jī)凸/凹類型 int shape = getNonceByRange(0, 1); //圓的標(biāo)準(zhǔn)方程 (x-a)2+(y-b)2=r2,標(biāo)識(shí)圓心(a,b),半徑為r的圓 //計(jì)算需要的小圖輪廓,用二維數(shù)組來表示,二維數(shù)組有兩張值,0和1,其中0表示沒有顏色,1有顏色 for (int i = 0; i < blockWidth; i++) { for (int j = 0; j < blockHeight; j++) { data[i][j] = 0; //創(chuàng)建中間的方形區(qū)域 if ((i >= blockRadius && i <= blockWidth - blockRadius && j >= blockRadius && j <= blockHeight - blockRadius)) { data[i][j] = 1; } double d1 = Math.pow(i - Objects.requireNonNull(circle1)[0], 2) + Math.pow(j - circle1[1], 2); double d2 = Math.pow(i - Objects.requireNonNull(circle2)[0], 2) + Math.pow(j - circle2[1], 2); //創(chuàng)建兩個(gè)凸/凹 if (d1 <= po || d2 <= po) { data[i][j] = shape; } } } return data; } /** * 根據(jù)朝向獲取圓心坐標(biāo) */ private static int[] getCircleCoords(int face, int blockWidth, int blockHeight, int blockRadius) { //上 if (0 == face) { return new int[]{blockWidth / 2 - 1, blockRadius}; } //左 else if (1 == face) { return new int[]{blockRadius, blockHeight / 2 - 1}; } //下 else if (2 == face) { return new int[]{blockWidth / 2 - 1, blockHeight - blockRadius - 1}; } //右 else if (3 == face) { return new int[]{blockWidth - blockRadius - 1, blockHeight / 2 - 1}; } return null; } /** * 在畫布上添加阻塞塊水印 */ private static void addBlockWatermark(BufferedImage canvasImage, BufferedImage blockImage, int x, int y) { Graphics2D graphics2D = canvasImage.createGraphics(); graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.8f)); graphics2D.drawImage(blockImage, x, y, null); graphics2D.dispose(); } /** * BufferedImage轉(zhuǎn)BASE64 */ public static String toBase64(BufferedImage bufferedImage, String type) { try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ImageIO.write(bufferedImage, type, byteArrayOutputStream); String base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); return String.format("data:image/%s;base64,%s", type, base64); } catch (IOException e) { System.out.println("圖片資源轉(zhuǎn)換BASE64失敗"); //異常處理 return null; } } }
2.5 滑動(dòng)驗(yàn)證碼Service
service層,封裝了工具類的一些方法,這里為了簡單沒寫接口,正常應(yīng)該是service和impl兩個(gè)類。
package com.thcb.captchademo.captcha.service; import com.thcb.captchademo.captcha.domain.Captcha; import com.thcb.captchademo.captcha.utils.CaptchaUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; import java.awt.image.BufferedImage; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * 滑動(dòng)驗(yàn)證碼Service * * @author thcb * @date 2023-05-25 */ @Service public class CaptchaService { /** * 拼圖驗(yàn)證碼允許偏差 **/ private static Integer ALLOW_DEVIATION = 3; @Autowired private StringRedisTemplate stringRedisTemplate; /** * 校驗(yàn)驗(yàn)證碼 * * @param imageKey * @param imageCode * @return boolean **/ public String checkImageCode(String imageKey, String imageCode) { ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); String key = "imageCode:" + imageKey; String text = ops.get(key); if (text == null || text.equals("")) { return "驗(yàn)證碼已失效"; } // 根據(jù)移動(dòng)距離判斷驗(yàn)證是否成功 if (Math.abs(Integer.parseInt(text) - Integer.parseInt(imageCode)) > ALLOW_DEVIATION) { return "驗(yàn)證失敗,請控制拼圖對齊缺口"; } // 驗(yàn)證成功,刪除redis緩存 stringRedisTemplate.delete(key); return null; } /** * 緩存驗(yàn)證碼,有效期1分鐘 * * @param key * @param code **/ public void saveImageCode(String key, String code) { ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); ops.set("imageCode:" + key, code, 60, TimeUnit.SECONDS); } /** * 獲取驗(yàn)證碼拼圖(生成的摳圖和帶摳圖陰影的大圖及摳圖坐標(biāo)) **/ public Object getCaptcha(Captcha captcha) { //參數(shù)校驗(yàn) CaptchaUtils.checkCaptcha(captcha); //獲取畫布的寬高 int canvasWidth = captcha.getCanvasWidth(); int canvasHeight = captcha.getCanvasHeight(); //獲取阻塞塊的寬高/半徑 int blockWidth = captcha.getBlockWidth(); int blockHeight = captcha.getBlockHeight(); int blockRadius = captcha.getBlockRadius(); //獲取資源圖 BufferedImage canvasImage = CaptchaUtils.getBufferedImage(captcha.getPlace()); //調(diào)整原圖到指定大小 canvasImage = CaptchaUtils.imageResize(canvasImage, canvasWidth, canvasHeight); //隨機(jī)生成阻塞塊坐標(biāo) int blockX = CaptchaUtils.getNonceByRange(blockWidth, canvasWidth - blockWidth - 10); int blockY = CaptchaUtils.getNonceByRange(10, canvasHeight - blockHeight + 1); //阻塞塊 BufferedImage blockImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR); //新建的圖像根據(jù)輪廓圖顏色賦值,源圖生成遮罩 CaptchaUtils.cutByTemplate(canvasImage, blockImage, blockWidth, blockHeight, blockRadius, blockX, blockY); // 移動(dòng)橫坐標(biāo) String nonceStr = UUID.randomUUID().toString().replaceAll("-", ""); // 緩存 saveImageCode(nonceStr, String.valueOf(blockX)); //設(shè)置返回參數(shù) captcha.setNonceStr(nonceStr); captcha.setBlockY(blockY); captcha.setBlockSrc(CaptchaUtils.toBase64(blockImage, "png")); captcha.setCanvasSrc(CaptchaUtils.toBase64(canvasImage, "png")); return captcha; } }
2.6 滑動(dòng)驗(yàn)證碼Controller
controller層有兩個(gè)方法,一個(gè)是獲取驗(yàn)證碼的方法,一個(gè)是驗(yàn)證碼校驗(yàn)的方法。
package com.thcb.captchademo.captcha.controller; import com.thcb.captchademo.captcha.domain.Captcha; import com.thcb.captchademo.captcha.domain.LoginVo; import com.thcb.captchademo.captcha.service.CaptchaService; import com.thcb.captchademo.captcha.utils.AjaxResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 滑動(dòng)驗(yàn)證碼Controller * * @author thcb * @date 2023-05-25 */ @RestController @RequestMapping("/captcha") public class CaptchaController { @Autowired private CaptchaService captchaService; @PostMapping("/slideCaptchaImage") public AjaxResult getCaptcha() { return AjaxResult.success(captchaService.getCaptcha(new Captcha())); } @PostMapping(value = "/login") public AjaxResult login(@RequestBody LoginVo loginVo) { // 驗(yàn)證碼校驗(yàn) String msg = captchaService.checkImageCode(loginVo.getNonceStr(), loginVo.getValue()); if (msg != null && !msg.equals("")) { return AjaxResult.error(msg); } return AjaxResult.success(); } }
3 總結(jié)
滑動(dòng)驗(yàn)證碼功能不算復(fù)雜,可以和項(xiàng)目當(dāng)前已有的驗(yàn)證碼共存,調(diào)用不同的接口,返回不同類型的驗(yàn)證碼,當(dāng)然這個(gè)就根據(jù)項(xiàng)目具體情況確定了。
以上就是Spring框架實(shí)現(xiàn)滑動(dòng)驗(yàn)證碼功能的代碼示例的詳細(xì)內(nèi)容,更多關(guān)于Spring滑動(dòng)驗(yàn)證碼功能的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
LeetCode -- Path Sum III分析及實(shí)現(xiàn)方法
這篇文章主要介紹了LeetCode -- Path Sum III分析及實(shí)現(xiàn)方法的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-10-10如何避免在Java項(xiàng)目里大批量使用if-else?
想起剛開始接觸JAVA時(shí),若遇到大量流程判斷語句,幾乎滿屏都是if-else語句,多得讓自己都忘了哪里是頭,哪里是尾,但是,縱然滿屏是if-else,但彼時(shí)也沒有覺得多別扭.等到編程能力漸漸提升之后,再回過頭去看曾經(jīng)寫過的滿屏if-else時(shí),感覺全都是翔.....,需要的朋友可以參考下2021-06-06Java集合和IO流實(shí)現(xiàn)水果攤項(xiàng)目
最近閑來無事,使用java基礎(chǔ)知識(shí)集合和IO流做了一個(gè)簡單的小項(xiàng)目,水果攤項(xiàng)目,用到GUI和Mysql數(shù)據(jù)庫搭建,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-06-06Springboot3+Redis實(shí)現(xiàn)消息隊(duì)列的多種方法小結(jié)
本文主要介紹了Springboot3+Redis實(shí)現(xiàn)消息隊(duì)列的多種方法小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03SpringBoot整合Dubbo+Zookeeper實(shí)現(xiàn)RPC調(diào)用
這篇文章主要給大家介紹了Spring Boot整合Dubbo+Zookeeper實(shí)現(xiàn)RPC調(diào)用的步驟詳解,文中有詳細(xì)的代碼示例,對我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-07-07SpringBoot?實(shí)現(xiàn)CAS?Server統(tǒng)一登錄認(rèn)證的詳細(xì)步驟
??CAS(Central?Authentication?Service)中心授權(quán)服務(wù),是一個(gè)開源項(xiàng)目,目的在于為Web應(yīng)用系統(tǒng)提供一種可靠的單點(diǎn)登錄,這篇文章主要介紹了SpringBoot?實(shí)現(xiàn)CAS?Server統(tǒng)一登錄認(rèn)證,需要的朋友可以參考下2024-02-02