开发者

Spring框架实现滑动验证码功能的代码示例

目录
  • 1. 整体描述
  • 2. 具体实现
    • 2.1 滑动验证码实体类
    • 2.2 滑动验证码登录VO
    • 2.3 滑动验证码接口返回类
    • 2.4 滑动验证码工具类
    • 2.5 滑动验证码Service
    • 2.6 滑动验证码Controller
  • 3 总结

    1. 整体描述

    之前项目需要在验证码模块,增加滑动验证码,用来给手机端使用的,大概看了下,主要方法就是将图片切割,然后记住偏移量,进行滑动,前端验证的时候,需要用前端传入的偏移量和生成的偏移量进行对比,如果在阈值之内,就验证通过,否则就不通过。具体实现方式见下文。之前没时间写,最近记录一下。

    2. 具体实现

    本工程主要依赖springboot框架,并且需要Redis存验证码的信息,还需要几个图片,用来生成验证码。

    2.1 滑动验证码实体类

    package com.thcb.captchademo.captcha.domain;
    
    import lombok.Data;
    
    
    /**
     * 滑动验证码
     *
     * @author thcb
     * @date 2023-05-25
     */
    
    @Data
    public class Captcha {
    
        /**
         * 随机字符串
         **/
        private String nonceStr;
        /**
         * 验证值
         **/
        private String value;
        /**
         * 生成的画布的base64
         **/
        private String canvasSrc;
        /**
         * 画布宽度
      php   **/
        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;
    }
    
    

    2.2 滑动验证码登录VO

    package com.thcb.captchademo.captcha.domain;
    
    import lombok.Data;
    
    /**
     * 滑动验证码登录Vo
     *
     * @author thcb
     * @date 2023-05-25
     */
    
    @Data
    public class LoginVo {
        /**
         * 随机字符串
         **/
        private String nonceStr;
        /**
         * 验证值
         **/
        private String value;
    }
    
    

    2.3 滑动验证码接口返回类

    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;
    
        /**
         * 状态码
         */
        public static final String CODE_TAG = "code";
    
        /**
         * 返回内容
         */
        public static final String MSG_TAG = "msg";
    
        /**
         * 数据对象
         */
        public static final String DATA_TAG = "data";
    
        /**
         * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
         */
        public AjaxResult() {
        }
    
        /**
         * 初始化一个新创建的 AjaxResult 对象
         *
         * @param code 状态码
         * @param msg  返回内容
         */
        public AjaxResult(int code, String msg) {
            super.put(CODE_TAG, code);
            super.put(MSG_TAG, msg);
        }
    
        /**
         * 初始化一个新创建的 AjaxResult 对象
         *
         * @param code 状态码
         * @param msg  返回内容
         * @param data 数据对象
         */
        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("操作成功");
        }
    
        /**
         * 返回成功数据
         *
         * @return 成功消息
         */
        public static AjaxResult success(Object data) {
            return AjaxResult.success("操作成功", data);
        }
    
        /**
         * 返回成功消息
         *
         * @param msg 返回内容
         * @return 成功消息
         */
        public static AjaxResult success(String msg) {
            return AjaxResult.success(msg, null);
        }
    
        /**
         * 返回成功消息
         *
         * @param msg  返回内容
         * @param data 数据对象
         * @return 成功消息
         */
        public static AjaxResult success(String msg, Object data) {
            return new AjaxResult(200, msg, data);
        }
    
        /**
         * 返回错误消息
         *
         * @return
         */
        public static AjaxResult error() {
            return AjaxResult.error("操作失败");
        }
    
        /**
         * 返回错误消息
         *
         * @param msg 返回内容
         * @return 警告消息
         */
        public static AjaxResult error(String msg) {
            return AjaxResult.error(msg, null);
        }
    
        /**
         * 返回错误消息
         *
         * @param msg  返回内容
         * @param data 数据对象
         * @return 警告消息
         */
        public static AjaxResult error(String msg, Object data) {
            return new AjaxResult(500, msg, data);
        }
    
        /**
         * 返回错误消息
         *
         * @param code 状态码
         * @param msg  返回内容
         * @return 警告消息
         */
        public static AjaxResult error(int code, String msg) {
            return new AjaxResult(code, msg, null);
        }
    }
    
    

    2.4 滑动验证码工具类

    此类是核心类,主要实现了图片的切割功能。

    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.Baseandroid64;
    import java.util.Objects;
    import java.util.Random;
    
    /**
     * 滑动验证码工具类
     *
     * @author thcb
     * @date 2023-05-25
     */
    public class CaptchaUtils {
    
        /**
         * 网络图片地址
         **/
        private final static String IMG_URL = "https://loyer.wang/view/ftp/wallpaper/%s.jpg";
    
        /**
         * 本地图片地址
         **/
        private final static String IMG_PATH = "E:\\caphcha\\%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(1);
            }
        }
    
        /**
         * 获取指定范围内的随机数
         **/
        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 {
                //随机图片
    
                //获取网络资源图片
                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;
            }
        }
    
        /**
         * 调整图片大小
         **/
        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
            Random random1 = new Random();
            int face1 = random1.nextInt(4);
            //凸/凹2
            int face2;
            //保证两个凸/凹不在同一位置
            do {
                Random random2 = new Random();
                face2 = random2.nextInt(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有颜www.devze.com色
            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;
            }
        }
    }
    

    2.5 滑动验证码Service

    service层,封装了工具类的一些方法,这里为了简单没写接口,正常应该是service和impl两个类。

    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;
    
    /**
     * 滑动验证码Service
     *
     * @author thcb
     * @date 2023-05-25
     */
    
    @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<String, String> ops = stringRedisTemplate.opsForValue();
            String key = "imageCode:" + imageKey;
            String text = ops.get(key);
            if (text == null || text.equals("")) {
                return "验证码已失效";
            }
            // 根据移动距离判断验证RlAQwaLj是否成功
            if (Math.abs(Integer.parseInt(text) - Integer.parseInt(imageCode)) > ALLOW_DEVIATION) {
                return "验证失败,请控制拼图对齐缺口";
            }
            // 验证成功,删除redis缓存
            stringRedisTemplate.delete(key);
            return null;
        }
    
        /**
         * 缓存验证码,有效期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);
        }
    
        /**
         * 获取验证码拼图(生成的抠图和带抠图阴影的大图及抠图坐标)
         **/
        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;
        }
    }
    

    2.6 滑动验证码Controller

    controller层有两个方法,一个是获取验证码的方法,一个是验证码校验的方法。

    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.Requeswww.devze.comtBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * 滑动验证码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) {
            // 验证码校验
            String msg = captchaService.checkImageCode(loginVo.getNonceStr(), loginVo.getValue());
            if (msg != null && !msg.equals("")) {
                return AjaxResult.error(msg);
            }
            return AjaxResult.success();
        }
    
    }
    
    

    3 总结

    滑动验证码功能不算复杂,可以和项目当前已有的验证码共存,调用不同的接口,返回不同类型的验证码,当然这个就根据项目具体情况确定了。

    以上就是Spring框架实现滑动验证码功能的代码示例的详细内容,更多关于Spring滑动验证码功能的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜