范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

看起来高大上的滑块验证码是怎样实现的?

  前言
  嗨,大家好,我是希留。
  验证码一直是各类网站登录和注册的一种校验方式,是用来防止有人恶意使用脚本批量进行操作从而设置的一种安全保护方式。随着近几年技术的发展,人们对于系统安全性和用户体验的要求越来越高,大多数网站系统都逐渐采用行为验证码来代替传统的图片验证码。
  今天这篇文章就来记录一下,我是如何实现从前端、到后端校验的整个流程的。  一、实现效果
  无图无真相,实现的效果如下图所示,点击登录后弹出一个弹出层,拼图是由后端生成的,拖动滑块位置,后端校验是否已拖动到指定的位置。
  二、实现思路
  整体的实现思路如下:  1、从服务器随机取一张底透明有形状的模板图,再随机取一张背景图、 2、随机生成抠图块坐标 3、根据步骤2的坐标点,对背景大图的抠图区域的颜色进行处理,新建的图像根据轮廓图颜色赋值,背景图生成遮罩层。 4、完成以上步骤之后得到两张图(扣下来的方块图,带有抠图区域阴影的原图),将这两张图和抠图区域的y坐标传到前台,x坐标存入redis。 5、前端在移动拼图时将滑动距离x坐标参数请求后台验证,服务器根据redis取出x坐标与参数的x进行比较,如果在伐值内则验证通过。如果滑动不成功,自动刷新图片,重置拼图,滑动成功,且账号密码正确就直接跳转到首页。 三、实现步骤1. 后端 java 代码1.1 新建一个拼图验证码类
  代码如下(示例):  @Data public class Captcha {      /**      * 随机字符串      **/     private String nonceStr;     /**      * 验证值      **/     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;     /**      * 阻塞块的横轴坐标      **/     private Integer blockX;     /**      * 阻塞块的纵轴坐标      **/     private Integer blockY;     /**      * 图片获取位置      **/     private Integer place; } 1.2 新建一个拼图验证码工具类
  代码如下(示例):  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();     }     /**      * BufferedImage转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("图片资源转换BASE64失败");             //异常处理             return null;         }     } } 1.3 新建一个 service 类
  代码如下(示例):  @Service public class CaptchaService {     /**      * 拼图验证码允许偏差      **/     private static Integer ALLOW_DEVIATION = 3;      @Autowired     private StringRedisTemplate stringRedisTemplate;      /**      * 校验验证码      * @param imageKey      * @param imageCode      * @return boolean      **/     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;     }     /**      * 缓存验证码,有效期15分钟      * @param key      * @param code      **/     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;     } } 1.4 新建一个 controller 类
  代码如下(示例):  @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));     }  }  1.5 登录接口
  代码如下(示例):      @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);     } 2. 前端 vue 代码2.1 新建一个 sliderVerify 组件
  代码如下(示例):        2.2 在登录页使用滑块组件
  代码如下(示例):           总结
  好了,以上就是本文的全部内容了,感谢大家的阅读。
  若觉得本文对你有帮助的话,还不忘点赞评论支持一下,感谢~

双十一的落寞,是时代的必然静悄悄地,2022年双十一就这样过去了。无论是现实生活中的喧闹,还是网络热搜数据,双十一都将成为历史,逐渐湮灭于那代人的记忆中。兴也忽也,亡也忽也!双十一何以兴亡如此迅速,自有其内明朝的制度十分严格,为何明朝亡于变态的政治制度呢?历朝历代的政治制度都有利有弊,而像明朝制度如此变态的却很少见。这种变态的制度也为明朝的灭亡埋下了祸根。明朝皇帝大多不信任大臣。无论是文臣还是武将,明朝皇帝都像防贼一样防着他们。为此雅典民主制度的发展克里斯提尼改革公元前546年,雅典产生了第一位僭主西特拉图。雅典人民很快发现将人生而应得权利寄托圣明的君主的恩赐并不是明智的选择,尽管西特拉图是一位较为开明的僭主,他致力于减少贵族特权,赞助文化被米粉怒喷半年后,小米终于认怂了开头问大伙儿一个问题。在这个手机厂商内卷严重的时间点,你们购买手机还会注重性能和堆料吗?小雷先来回答,如果手机各方面没啥短板,我还是愿意会买单的。实际也是如此,大概在一年半前,我购通信协议CAN的位同步和帧格式解析(二)上一篇文章已经简述了CAN总线的一些基本特性和数据的时序位的构成,接下来分析CAN总线的协议构成!CAN的位同步数据同步(1)CAN的位同步在CAN中使用了位同步的方式来抵抗干扰吸大白话什么是Web3。0最近火得一塌糊涂的web3。0概念股都炒的鸡毛飞上天了,并且在不久的将来还可能继续火下去。天地在线,榕基软件,福石控股,天下秀等都狠狠地炒了一波。在搞清楚什么时候Web3。0之前首特斯拉不够个性怎么办?改装厂交给我呀!电动汽车问世后,加速性能突然间崩盘,成了极为廉价的东西。而在众多电动汽车品牌中,特斯拉又是其中的独角兽,所以很多原本打算花费重金购买顶级超跑的人,扭头选择了特斯拉。但是特斯拉也有自情感故事绣着蓝蜻蜓的白手帕在我的记忆深处,一场情感的风暴始终挥之不去。不久前深秋的一个夜晚,窗外的雨淅淅沥沥地下个不停,好像永远也扯不断的帘,在拉拽着我心灵深处的那段记忆。也许是人老了,喜欢回忆往事。在这个最适合旅行的摩旅神车长途旅行的摩友,在选择摩托车作为交通工具出行的时候都会想到一些拉力车巡航车之类的车型,而踏板车也是比较适合于长途的摩旅的,对于踏板车的优势来看,骑行都比较舒适,而宽大的坐垫设计也是协同新办公全力助增长以数字之力提升企业竞争势能疫情催化之下,协同办公理念逐步升级,伴随企业进入到以数字办公为基础的新周期。以提升管理效率为目的的协同办公在人工智能大数据等新技术的加持下,快速步入赋能业务提升业务效率的自律型协作杨颖倪妮高圆圆陈妍希张小斐关晓彤柳岩姚晨大秀来了11月12日,第35届金鸡奖红毯隆重举办,这次的红毯秀很有面子和排场,请来了很多大咖和明星,刘德华也来了,苏有朋久违走红毯,高圆圆很久没走过红毯了吧,这次也盛装亮相了这次的红毯。这
钱天一战胜伊藤美诚世界乒乓球职业大联盟(WTT)新加坡大满贯赛13日展开单打第二轮较量。中国队选手钱天一挽救两个赛点,在女单比赛中32险胜日本名将伊藤美诚。卫冕冠军陈梦23不敌罗马尼亚选手萨马拉,止拒绝掉链子!林高远32淘汰韩国一哥,陈幸同30,感谢马琳指点北京时间3月14日,国际乒联WTT新加坡大满贯站比赛正在进行。正在进行的是男单比赛,国乒林高远对阵韩国一哥张禹珍。双方都是球迷非常熟悉的运动员,此前两人交手过两次,各胜一场。林高远会里会外丨武增管法的法再修正,这些亮点值得关注正在进行的十四届全国人大一次会议有一项重要议程,就是审议中华人民共和国立法法(修正草案)。会议期间,代表们将就全国人民代表大会关于修改立法法的决定草案进行表决。立法法是规范国家立法李玮锋曾鼓励郜林深足很难但你要坚持,别像我连退役仪式都没有直播吧3月14日讯记者赵宇表示,去年联赛时李玮锋与郜林在赛区碰面,李玮锋拍着郜林的肩膀说,深圳很难但你郜林要坚持,别像他一样连个退役仪式都没有,生涯不那么圆满。前深足外援卡尔德克表女子工厂组建临时夫妻,年后想再续前缘,不料男人的正妻来了为了生计,很多人不得不选择外出打工,我只能选择在家乡打工,身心都处于极度疲惫和空虚的状态。为了解决这个问题,不少人偷偷开始制造临时夫妻。网上对临时夫妻现象的描述是这样的临时夫妻是一谁发明了0这个数字?0是自然数么,是质数还是合数么?一谁发明了0这个数字0的起源可以追溯到古印度的数学家和天文学家,他们最早使用了类似于0的记号来表示空位或无。然而,这些符号并不是我们现在使用的0。实际上,0的符号最早出现在公元7世未来学家表示,人类有望在7年内实现不朽随着人工智能的不断发展,未来学家雷库兹韦尔(RayKurzweil)在其最初的奇点理论发表多年后,仍在掀起波澜。随着奇点里程碑的到来,库兹韦尔相信到2030年,不朽是可以实现的。库调查问界改姓HUAWEI背后AITO和问界商标到底属于谁?出品搜狐汽车汽车咖啡馆3月8日,AITO汽车在多个平台的宣传文案中,一改一贯使用的AITO问界,开始使用HUAWEI问界,引发外界巨大关注。尽管华为和赛力斯后续都已回应表态称双方将Mac是最好的Linux,Win也是最好的Linux,只有Linux做不好Linux尽管条条道路通罗马,但毕竟有的路走得更平稳更快捷,更不要说有的人甚至就住在罗马。对于Python开发而言,你的开发环境有多好用,你离罗马就有多近。因此,我们的旅程从这里开始。本章首5G远程手术机器人引爆互联网后的下一步聚焦科创板日报3月14日讯(记者徐红)由于保持了较强的研发临床及商业化投入,国产手术机器人龙头微创机器人B在2022年的亏损幅度进一步扩大。2021年净亏损5。84亿元,而到了2022潍城区开展教育数字化种子教师培育工程人勤春来早,功到秋华实近日,潍城区教育数字化种子教师培育工程启动会议在潍坊市教育大数据中心举行,来自全区56处中小学的58名教师参加。潍城区教育数字化种子教师培育工程,是在潍坊市教育信息化研究院的指导下