java实现滑动验证码

准备工作
1、若干原图片(下文称为原图)
2、一张抠图形状图片(下文称为滑块模板,抠出来的图称为滑块)
3、一张抠图边框图片(下文称为滑块边框)
注意:滑块模板尺寸必须小于原图尺寸

实现思路
1、后端 - 在若干原图中随机一张原图
2、后端 - 根据滑块模板随机在原图中随机一个矩形区域,并且得到区域x、y坐标,注意随机区域不要超过原图边界
3、后端 - 根据滑块模板在原图上抠图(原理:滑块模板中滑块是有颜色的,根据滑块模板坐标可以直接取到原图坐标,然后在取原图坐标点颜色并赋值到滑块)
4、后端 - 根据滑块模板在原图上设置遮罩缺块(原理:同上取到原图坐标并取得原图色值,把色值调低并放回去就形成遮罩缺块)
5、后端 - 为了用户体验给滑块加边框(原理:滑块边框与滑块模板同等画布,且边框刚好大于滑块,直接取边框中有颜色的坐标,并把颜色设置在滑块上)
6、后端 - 把滑块图片Base64,有缺块的原图Base64
7、后端 - 经过前6步已经可以得到,两个Base64的字符串和一个坐标x、y,随机一个key,把坐标x、y存在redis中
8、后端 - 返回前端两个Base64的字符串和y坐标 和key
9、前端 - 每隔100毫秒获取一次滑块坐标,滑动结束后把key和路径坐标数组返回给后端
10、后端 - 得到key和路径数组后,从redis中取出缺块坐标,用数组中最后一个坐标与缺块坐标比较,差值是否在预设差值范围内,如果不在直接验证失败
11、(可不选)后端 - 为防止机器学习模拟滑块滑动,可以做路径校验,校验原理开始慢,中间快,最后慢(需要慢慢调法值,这里省略)

 

滑块模板 

滑块边框

背景图


代码:

public class ImageSlideVerification {

    /**
     * 源图目录路径
     */
    private static String ORIGIN_CATALOG = "original";

    /**
     * 模板图目录路径
     */
    private static String TEMPLATE_CATALOG = "templates";

    /**
     * 描边图目录路径
     */
    private static String BORDER_CATALOG = "templates";

    /**
     * Image-key
     */
    private static String IMAGE_KEY = "IMAGE:KEY:";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 获取滑动验证码
     * @return
     */
    public SlideVerificationVO getSlideCode() {
        //  原图
        File originalFile = getOriginalFile();
        //  滑块模板图
        File templateFile = getTemplateFile();
        //  滑块描边图片
        File borderFile = getBorderFile();

        // 生成随机x、y
        int x = generateX(originalFile, templateFile);
        int y = generateY(originalFile, templateFile);

        // 生成滑块
        String slide = cutSlideImage(originalFile, templateFile, borderFile, x, y);

        // 生成缺块图
        String back = cutBackImage(originalFile, templateFile, borderFile, x, y);

        // 生成可以
        String imageToken = UUID.randomUUID().toString().replace("-", "");

        // 放入redis
        Map<String, Integer> cacheMap = new HashMap<>();
        cacheMap.put("x", x);
        cacheMap.put("y", y);
        redisTemplate.opsForValue().set(IMAGE_KEY + imageToken, JSONObject.toJSONString(cacheMap), 5, TimeUnit.MINUTES);

        SlideVerificationVO vo = new SlideVerificationVO();
        vo.setImageToken(imageToken);
        vo.setBackImage(back);
        vo.setSlideImage(slide);
        vo.setY(y + "");

        return vo;
    }

    /**
     * 验证滑动验证码
     * @param imageToken
     * @param imageCode 数据结构 [{x:0,y:0},{x:10,y:10},{x:20,y:20}]
     * @return
     */
    private boolean checkSlideCode(String imageToken, String imageCode) {
        // redis 取值
        String json = redisTemplate.opsForValue().get(IMAGE_KEY + imageToken);
        if (StringUtils.isEmpty(json)) {
            return false;
        }

        // 获取缓存中的x、y
        Map<String, Integer> cacheMap = JSONObject.parseObject(json, Map.class);
        int x = cacheMap.get("x");
        int y = cacheMap.get("y");

        // 获取参数中最后一个元素
        List<Map> imageCodeList = JSONObject.parseArray(imageCode, Map.class);
        Map codeMap = imageCodeList.get(imageCodeList.size() - 1);
        Object codeX = codeMap.get("x");
        Object codeY = codeMap.get("y");
        if (codeX == null || codeY == null) {
            return false;
        }

        // 验证 x
        int errorX = Integer.parseInt(codeX.toString()) - x;
        if (errorX > 1 || errorX < -1) {
            return false;
        }

        // 验证 y
        int errorY = Integer.parseInt(codeY.toString()) - y;
        if (errorY > 1 || errorY < -1) {
            return false;
        }

        // *****验证路径*****
        // *****验证路径*****

        return true;
    }

    /**
     * 获取跟目录
     * @return
     */
    private static String getClassPath() {
        return ImageSlideVerification.class.getClass().getResource("/").getPath();
    }

    /**
     * 获取随机原图图片
     */
    private static File getOriginalFile() {
        StringBuilder builder = new StringBuilder(getClassPath());
        builder.append(ORIGIN_CATALOG);
        builder.append("/");
        builder.append(new Random().nextInt(10));
        builder.append(".png");

        return new File(builder.toString());
    }

    /**
     * 滑块模板图片
     */
    private static File getTemplateFile() {
        StringBuilder builder = new StringBuilder(getClassPath());
        builder.append(TEMPLATE_CATALOG);
        builder.append("/");
        builder.append("template.png");

        return new File(builder.toString());
    }

    /**
     * 获取描边原图图片
     */
    private static File getBorderFile() {
        StringBuilder builder = new StringBuilder(getClassPath());
        builder.append(BORDER_CATALOG);
        builder.append("/");
        builder.append("border.png");

        return new File(builder.toString());
    }

    /**
     * 随机X坐标
     * @param originalFile
     * @param templateFile
     * @return
     */
    private static int generateX(File originalFile , File templateFile) {
        try {
            BufferedImage originalImage = ImageIO.read(originalFile);
            BufferedImage templateImage = ImageIO.read(templateFile);

            //  原图宽度
             int width = originalImage.getWidth();

            //  模板宽度
            int templateImageWidth = templateImage.getWidth();
            Random random = new Random(System.currentTimeMillis());

            return random.nextInt(width - templateImageWidth) % (width - templateImageWidth - templateImageWidth + 1) + templateImageWidth;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 随机Y坐标
     * @param originalFile
     * @param templateFile
     * @return
     */
    private static int generateY(File originalFile , File templateFile) {
        try {
            BufferedImage originalImage = ImageIO.read(originalFile);
            BufferedImage templateImage = ImageIO.read(templateFile);
            //  原图高度
             int height = originalImage.getHeight();

            //  抠图模板高度
            int templateImageHeight = templateImage.getHeight();
            Random random = new Random(System.currentTimeMillis());
            if (templateImageHeight - height >= 0) {
                return random.nextInt(10);
            }

            return random.nextInt(height - templateImageHeight) % (height - templateImageHeight - templateImageHeight + 1) + templateImageHeight;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 裁剪滑块
     * @param originalFile
     * @param templateFile
     * @param borderFile
     * @param x
     * @param y
     * @return
     */
    private static String cutSlideImage(File originalFile , File templateFile, File borderFile, int x, int y) {
        ImageInputStream originImageInputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            BufferedImage templateImage = ImageIO.read(templateFile);
            BufferedImage borderImage = ImageIO.read(borderFile);

            int with = templateImage.getWidth();
            int height = templateImage.getHeight();

            //  创建滑块
            BufferedImage cutoutImage = new BufferedImage(with, height, templateImage.getType());

            // 从原图中获取与滑块一样大小的4边形图片
            ImageReader originalReader = ImageIO.getImageReadersByFormatName("png").next();
            originImageInputStream = ImageIO.createImageInputStream(originalFile);
            //  图片输入流顺序读写
            originalReader.setInput(originImageInputStream, true);

            //  根据坐标生成矩形
            ImageReadParam imageReadParam = originalReader.getDefaultReadParam();
            Rectangle rectangle = new Rectangle(x, y, with, height);
            imageReadParam.setSourceRegion(rectangle);

            // 从原图中裁剪下来的图片
            BufferedImage cutImage = originalReader.read(0, imageReadParam);

            // 获取剪切图片矩阵颜色
            int[][] cutColorArray = getColorArray(cutImage);
            // 获取模板图片矩阵颜色
            int[][] templateColorArray = getColorArray(templateImage);
            // 获取边框图片矩阵颜色
            int[][] borderColorArray = getColorArray(borderImage);

            // 将模板图非透明像素设置到剪切图中
            for (int i = 0; i < templateColorArray.length; i++) {
                for (int j = 0; j < templateColorArray[0].length; j++) {
                    // 滑块模板颜色不为透明时设置剪切图颜色
                    int templateRGB = templateColorArray[i][j];
                    if (templateRGB != 16777215 && templateRGB < 0) {
                        cutoutImage.setRGB(i, j, cutColorArray[i][j]);
                    }
                    // 设置边框颜色
                    int borderRGB = borderColorArray[i][j];
                    if (borderRGB != 16777215 && borderRGB < 0) {
                        int[] colorArray = {(borderColorArray[i][j] & 0xff0000) >> 16,
                                (borderColorArray[i][j] & 0xff00) >> 8,
                                (borderColorArray[i][j] & 0xff)};

                        // 我的边框是黑色所以我这是加
                        colorArray[0] += (cutColorArray[i][j] & 0xff0000) >> 16;
                        colorArray[1] += (cutColorArray[i][j] & 0xff00) >> 8;
                        colorArray[2] += (cutColorArray[i][j] & 0xff);

                        cutoutImage.setRGB(i, j , new Color(colorArray[0] > 255 ? 255 : colorArray[0],
                                colorArray[1] > 255 ? 255 : colorArray[1],
                                colorArray[2] > 255 ? 255 : colorArray[2]).getRGB());
                    }
                }
            }

            //  图片绘图
            int bold = 5;
            Graphics2D graphics = cutoutImage.createGraphics();
            graphics.setBackground(Color.white);

            //  设置抗锯齿属性
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
            graphics.drawImage(cutoutImage, 0, 0, null);
            graphics.dispose();

            // 输出图片流
            byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(cutoutImage, "png", byteArrayOutputStream);
            //  图片转为二进制字符串
            byte[] cutoutImageBytes = byteArrayOutputStream.toByteArray();
            byteArrayOutputStream.reset();
            //  图片加密成base64字符串
            return Base64.getUrlEncoder().encodeToString(cutoutImageBytes);
        } catch (IOException e) {
                e.printStackTrace();
        } finally {
            try {
                if (originImageInputStream != null) {
                    originImageInputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    /**
     * 生成背景图
     * @param originalFile
     * @param templateFile
     * @param borderFile
     * @param x
     * @param y
     * @return
     */
    private static String cutBackImage(File originalFile , File templateFile, File borderFile, int x, int y) {
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {

            BufferedImage originalImage = ImageIO.read(originalFile);
            BufferedImage templateImage = ImageIO.read(templateFile);

            int with = templateImage.getWidth();
            int height = templateImage.getHeight();

            //  根据原图,创建支持alpha通道的rgb图片
            BufferedImage shadeImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_INT_ARGB);

            // 获取原图片矩阵
            int[][] originColorArray = getColorArray(originalImage);

            //  将原图的像素拷贝到遮罩图
            for (int i = 0; i < originColorArray.length; i++) {
                for (int j = 0; j < originColorArray[0].length; j++) {
                    int originalRGB = originalImage.getRGB(i, j);

                    // 在模板区域内
                    if (i > x && i < (x + with) && j > y && j < (y + height)) {
                        int templateRGB = templateImage.getRGB(i - x, j - y);
                        //对源文件备份图像(x+i,y+j)坐标点进行透明处理
                        if (templateRGB != 16777215 && templateRGB < 0) {

                            int[] rgb = new int[3];
                            rgb[0] = (originalRGB & 0xff0000) >> 16;
                            rgb[1] = (originalRGB & 0xff00) >> 8;
                            rgb[2] = (originalRGB & 0xff);

                            // 对遮罩透明处理
                            shadeImage.setRGB(i, j, new Color(rgb[0]/3,rgb[1]/3,rgb[2]/3).getRGB());
                            continue;
                        }
                    }

                    //  无透明处理
                    shadeImage.setRGB(i, j, originalRGB);
                }
            }

            // 输出图片流
            byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(shadeImage, "png", byteArrayOutputStream);
            //  图片转为二进制字符串
            byte[] cutoutImageBytes = byteArrayOutputStream.toByteArray();
            byteArrayOutputStream.reset();
            //  图片加密成base64字符串
            return Base64.getUrlEncoder().encodeToString(cutoutImageBytes);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    /**
     * 图片生成图像矩阵
     * @param bufferedImage  图片源
     * @return 图片矩阵
     */
    private static int[][] getColorArray(BufferedImage bufferedImage) {
        int[][] matrix = new int[bufferedImage.getWidth()][bufferedImage.getHeight()];
        for (int i = 0; i < bufferedImage.getWidth(); i++) {
            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                matrix[i][j] = bufferedImage.getRGB(i, j);
            }
        }
        return matrix;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //  原图
                    File originalFile = getOriginalFile();
                    //  模板图
                    File templateFile = getTemplateFile();
                    //  描边图片
                    File borderFile = getBorderFile();

                    int x = generateX(originalFile, templateFile);
                    int y = generateY(originalFile, templateFile);

                    String slide = cutSlideImage(originalFile, templateFile, borderFile, x, y);
                    String back = cutBackImage(originalFile, templateFile, borderFile, x, y);

                    String imageToken = UUID.randomUUID().toString().replace("-", "");
                    SlideVerificationVO vo = new SlideVerificationVO();
                    vo.setImageToken(imageToken);
                    vo.setBackImage(back);
                    vo.setSlideImage(slide);
                    vo.setY(y + "");

                    System.out.println(JSONObject.toJSONString(vo));
                }
            }).start();
        }
    }

}

效果:

 

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

源码猎人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值