文章目录 前言一、实现效果二、实现思路三、实现步骤1. 后端 java 代码1.1 新建一个拼图验证码类1.2 新建一个拼图验证码工具类1.3 新建一个 service 类1.4 新建一个 controller 类1.5 登录接口
嗨,大家好,我是希留。
验证码一直是各类网站登录和注册的一种校验方式,是用来防止有人恶意使用脚本批量进行操作从而设置的一种安全保护方式。随着近几年技术的发展,人们对于系统安全性和用户体验的要求越来越高,大多数网站系统都逐渐采用行为验证码来代替传统的图片验证码。
今天这篇文章就来记录一下,我是如何实现从前端、到后端校验的整个流程的。
无图无真相,实现的效果如下图所示,点击登录后弹出一个弹出层,拼图是由后端生成的,拖动滑块位置,后端校验是否已拖动到指定的位置。
整体的实现思路如下:
代码如下(示例):
@Datapublic class Captcha { private String nonceStr; private String value; private String canvasSrc; private Integer canvasWidth; private Integer canvasHeight; private String blockSrc; private Integer blockWidth; private Integer blockHeight; private Integer blockRadius; private Integer blockX; private Integer blockY; private Integer place;}
代码如下(示例):
public class CaptchaUtils { private final static String IMG_URL = "https://loyer.wang/view/ftp/wallpaper/%s.jpg"; private final static String IMG_PATH = "E:/Temp/wallpaper/%s.jpg"; public static void checkCaptcha(Captcha captcha) { //设置画布宽度默认值 if (captcha.getCanvasWidth() == null) { captcha.setCanvasWidth(320); } //设置画布高度默认值 if (captcha.getCanvasHeight() == null) { captcha.setCanvasHeight(155); } //设置阻塞块宽度默认值 if (captcha.getBlockWidth() == null) { captcha.setBlockWidth(65); } //设置阻塞块高度默认值 if (captcha.getBlockHeight() == null) { captcha.setBlockHeight(55); } //设置阻塞块凹凸半径默认值 if (captcha.getBlockRadius() == null) { captcha.setBlockRadius(9); } //设置图片来源默认值 if (captcha.getPlace() == null) { captcha.setPlace(0); } } public static int getNonceByRange(int start, int end) { Random random = new Random(); return random.nextInt(end - start + 1) + start; } public static BufferedImage getBufferedImage(Integer place) { try { //随机图片 int nonce = getNonceByRange(0, 1000); //获取网络资源图片 if (0 == place) { String imgUrl = String.fORMat(IMG_URL, nonce); URL url = new URL(imgUrl); return Imageio.read(url.openStream()); } //获取本地图片 else { String imgPath = String.format(IMG_PATH, nonce); File file = new File(imgPath); return ImageIO.read(file); } } catch (Exception e) { System.out.println("获取拼图资源失败"); //异常处理 return null; } } 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); //创建阻塞块具体形状 for (int i = 0; i < blockWidth; i++) { for (int j = 0; j < blockHeight; j++) { try { //原图中对应位置变色处理 if (blockData[i][j] == 1) { //背景设置为黑色 waterImage.setRGB(i, j, Color.BLACK.getRGB()); blockImage.setRGB(i, j, canvasImage.getRGB(blockX + i, blockY + j)); //轮廓设置为白色,取带像素和无像素的界点,判断该点是不是临界轮廓点 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()); } } //这里把背景设为透明 else { blockImage.setRGB(i, j, Color.TRANSLUCENT); waterImage.setRGB(i, j, Color.TRANSLUCENT); } } catch (ArrayIndexOutOfBoundsException e) { //防止数组下标越界异常 } } } //在画布上添加阻塞块水印 addBlockWatermark(canvasImage, waterImage, blockX, blockY); } private static int[][] getBlockData(int blockWidth, int blockHeight, int blockRadius) { int[][] data = new int[blockWidth][blockHeight]; double po = Math.pow(blockRadius, 2); //随机生成两个圆的坐标,在4个方向上 随机找到2个方向添加凸/凹 //凸/凹1 int face1 = RandomUtils.nextInt(0,4); //凸/凹2 int face2; //保证两个凸/凹不在同一位置 do { face2 = RandomUtils.nextInt(0,4); } while (face1 == face2); //获取凸/凹起位置坐标 int[] circle1 = getCircleCoords(face1, blockWidth, blockHeight, blockRadius); int[] circle2 = getCircleCoords(face2, blockWidth, blockHeight, blockRadius); //随机凸/凹类型 int shape = getNonceByRange(0, 1); //圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆 //计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色 for (int i = 0; i < blockWidth; i++) { for (int j = 0; j < blockHeight; j++) { data[i][j] = 0; //创建中间的方形区域 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); //创建两个凸/凹 if (d1 <= po || d2 <= po) { data[i][j] = shape; } } } return data; } 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(); } 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("图片资源转换BASE64失败"); //异常处理 return null; } }}
代码如下(示例):
@Servicepublic class CaptchaService { private static Integer ALLOW_DEVIATION = 3; @Autowired private StringRedisTemplate stringRedisTemplate; public String checkImageCode(String imageKey, String imageCode) { ValueOperations ops = stringRedisTemplate.opsForValue(); String text = ops.get("imageCode:" + imageKey); if(StrUtil.isBlank(text)){ return "验证码已失效"; } // 根据移动距离判断验证是否成功 if (Math.abs(Integer.parseInt(text) - Integer.parseInt(imageCode)) > ALLOW_DEVIATION) { return "验证失败,请控制拼图对齐缺口"; } return null; } public void saveImageCode(String key, String code) { ValueOperations ops = stringRedisTemplate.opsForValue(); ops.set("imageCode:" + key, code, 15, TimeUnit.MINUTES); } public Object getCaptcha(Captcha captcha) { //参数校验 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()); //调整原图到指定大小 canvasImage = CaptchaUtils.imageResize(canvasImage, canvasWidth, canvasHeight); //随机生成阻塞块坐标 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); //新建的图像根据轮廓图颜色赋值,源图生成遮罩 CaptchaUtils.cutByTemplate(canvasImage, blockImage, blockWidth, blockHeight, blockRadius, blockX, blockY); // 移动横坐标 String nonceStr = UUID.randomUUID().toString().replaceAll("-", ""); // 缓存 saveImageCode(nonceStr,String.valueOf(blockX)); //设置返回参数 captcha.setNonceStr(nonceStr); captcha.setBlockY(blockY); captcha.setBlockSrc(CaptchaUtils.toBase64(blockImage, "png")); captcha.setCanvasSrc(CaptchaUtils.toBase64(canvasImage, "png")); return captcha; }}
代码如下(示例):
@RestController@RequestMapping("/captcha")public class CaptchaController { @Autowired private CaptchaService captchaService; @apiOperation(value = "生成验证码拼图") @PostMapping("get-captcha") public R getCaptcha(@RequestBody Captcha captcha) { return R.ok(captchaService.getCaptcha(captcha)); }}
代码如下(示例):
@ApiOperation(value = "登录") @PostMapping(value = "login") public R login(@RequestBody LoginVo loginVo) { // 只有开启了验证码功能才需要验证 if (needAuthCode) { String msg = captchaService.checkImageCode(loginVo.getNonceStr(),loginVo.getValue()); if (StringUtils.isNotBlank(msg)) { return R.error(msg); } } String token = loginService.login(loginVo.getUserName(), loginVo.getPassWord()); if (StringUtils.isBlank(token)) { return R.error("用户名或密码错误"); } Map tokenMap = new HashMap<>(); tokenMap.put("token", token); tokenMap.put("tokenHead", tokenHead); return R.ok(tokenMap); }
代码如下(示例):
代码如下(示例):
...省略无关代码 登录
好了,以上就是本文的全部内容了,感谢您的阅读。
若觉得本文对你有帮助的话,还不忘点赞评论支持一下,感谢~
来源地址:https://blog.csdn.net/qq_30859353/article/details/126747608
--结束END--
本文标题: 登录校验之滑块验证码完整实现(vue + springboot)
本文链接: https://lsjlt.com/news/373874.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-04-01
2024-04-03
2024-04-03
2024-01-21
2024-01-21
2024-01-21
2024-01-21
2023-12-23
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0