使用顺序点击汉字验证码方式登录,验证码图片的生成java

项目要求修改原本验证码登录为顺序点击图片验证码登录
验证码登录:
图片验证码登录
顺序点击汉字验证码登录:
在这里插入图片描述
这里就不讲解前端样式如何做的了,因为我是前后端分离,所以只做后端接口开发,将后台生成的图片返回给前台,前台监控鼠标,按照点击所获得的坐标进行保存,并发送给后台,后台生成图片验证码时会将汉字的坐标存储到redis或者ehcache中做一个缓存,当校验的请求进入之后,对前后端的坐标进行校验,成功后,然后校验密码,如果密码正确登录成功。
设计图如下:(涉及到一些业务场景不完全相同)
在这里插入图片描述
直接使用流将图片返回给前台,前台使用如下示例展示图片

<img data-v-d649224e="" src="/demo/drawChinesePhote?time=1610359826578"" class="loginverifyimg" style="cursor: pointer;">

controller中返回图片的代码

 BufferedImage image = drawChineseService.draw(codeCount, token, iWidth, iHeight);
                ehcacheLoginTimesService.addLoginTime(ip, times == null ? 1 : times + 1);
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                response.setDateHeader("Expires", 0);
                response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
                response.addHeader("Cache-Control", "post-check=0, pre-check=0");
                response.setHeader("Pragma", "no-cache");
                try {
                    ImageIO.write(image, "png", stream);
                    BASE64Encoder encoder = new BASE64Encoder();
                    base64 = encoder.encodeBuffer(stream.toByteArray());
                    base64 = base64.replaceAll("\n", "").replaceAll("\r", "");
                    object.put("data", "data:image/png;base64," + base64);
                    object.put("code", "200");
                    object.put("number", codeCount);
                    response.getWriter().write(object.toString());
                } catch (IOException e) {
                    response.getWriter().write("");
                } finally {
                    if (stream != null) {
                        stream.flush();
                        stream.close();
                        response.getWriter().close();
                    }
                }

调用service层解决业务逻辑

 public BufferedImage draw(int count, String token, int iWidth, int iHeight) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug("生成图片的iWidth:" + iWidth + "------iHeight:" + iHeight);
        }
        //开始画图返回image和坐标对象
        DrawChinese drawChinese = DrawUtil.createChineseImage(count, iWidth, iHeight,chineseErrorSize);
        //获取坐标list
        List<Coords> coordsList = drawChinese.getCoordsList();
        if (logger.isDebugEnabled()) {
            logger.debug("coordsList存取前:" + coordsList);
        }
        //坐标list存入redis
        loginService.saveXYZList(token, coordsList);
        return drawChinese.getImage();
    }

生成图片的工具类

public static DrawChinese createChineseImage(int count, int iWidth, int iHeight,int chineseErrorSize) throws IOException {
        DrawChinese drawChinese = new DrawChinese();
        Random random = new Random();
        BufferedImage image = new BufferedImage(iWidth, iHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = (Graphics2D) image.getGraphics();
        ClassPathResource classPathResource = new ClassPathResource("png" + File.separator + "picture.png");
        g.setBackground(Color.white);
        //File picPath= classPathResource.getFile();  //读取本地图片,做背景图片
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        InputStream inputStream = classPathResource.getInputStream();
        //生成目标文件
        File somethingFile = File.createTempFile("DailyReportTemplate", ".png");
        try {
            FileUtils.copyInputStreamToFile(inputStream, somethingFile);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }

        g.drawImage(ImageIO.read(somethingFile), 0, 0, iWidth, iHeight, null); //将背景图片从高度20开始
        //g.setColor(Color.white);  //设置颜色
        g.drawRect(0, 0, iWidth - 1, iHeight - 1); //画边框
        AffineTransform affineTransform = new AffineTransform();
        Font font = new Font("华文行楷", Font.PLAIN, 40);

        Integer x = null, y = null;  //用于记录坐标
        String target = null; // 用于记录文字
        List<Coords> coordsList = new ArrayList<Coords>();
        int a = 0;
        int b = 0;
        for (int i = 0; i < count; i++) {  //随机产生4个文字,坐标,颜色都不同
            //g.setColor(new Color(random.nextInt(50) + 200, random.nextInt(150) + 100, random.nextInt(50) + 200));
            g.setColor(new Color(random.nextInt(50) + 100, random.nextInt(200), random.nextInt(200)));
            String str = getRandomChineseChar();
            int listx, listy;
            int j = 0;
            Coords coords = new Coords();
            boolean flag = true;
            //校验,如果字体坐标重复在一起影响美观
            while (true) {
                if (flag) {
                    a = random.nextInt(iWidth - 80) + 40;
                    b = random.nextInt(iHeight - 80) + 40;
                    flag = false;
                }
                if (j < coordsList.size()) {
                    listx = coordsList.get(j).getX();
                    listy = coordsList.get(j).getY();
                    //判断坐标如果相等直接重新生成
                    if (listx == a && listx == b) {
                        flag = true;
                    }
                    //判断字体是否会覆盖
                    if (!((listx - chineseErrorSize <= a) && (listx + chineseErrorSize >= a) &&
                            (listy - chineseErrorSize <= b) && (listy + chineseErrorSize >= b))) {
                        if (j == coordsList.size() - 1) {
                            coords.setX(a);
                            coords.setY(b);
                            //保存坐标
                            coordsList.add(coords);
                            break;
                        }
                        j++;
                    } else {
                        flag = true;
                    }


                } else {
                    coords.setX(a);
                    coords.setY(b);
                    //保存坐标
                    coordsList.add(coords);
                    break;
                }
            }
            Font rotatedFont = font.deriveFont(affineTransform);
            affineTransform.rotate(Math.toRadians(random.nextInt(45)), 0, 0);
            g.setFont(rotatedFont);
            g.drawLine(a + 5, b - 4, random.nextInt(iWidth - 100) + 50, random.nextInt(iHeight - 70) + 55);                //设置干扰线的坐标

            if (target == null) {
                target = str; //记录第一个文字
            } else {
                target = target + str;
            }
            g.drawString(str, a, b);
        }
        g.setColor(Color.black);
        g.setFont(new Font("华文仿宋", Font.BOLD, 20)); //设置字体
        g.drawString(target, 0, 17);//写入验证码第一行文字  “点击..”
        //5.释放资源
        g.dispose();
        drawChinese.setCoordsList(coordsList);
        drawChinese.setImage(image);
        return drawChinese;
    }
    

返回的drawChinese是一个DrawChinese对象,

public class DrawChinese {
    private List<Coords> coordsList;
    private BufferedImage image;

    public List<Coords> getCoordsList() {
        return coordsList;
    }

    public BufferedImage getImage() {
        return image;
    }

    public void setCoordsList(List<Coords> coordsList) {
        this.coordsList = coordsList;
    }

    public void setImage(BufferedImage image) {
        this.image = image;
    }

    @Override
    public String toString() {
        return "DrawChinese{" +
                "coordsList=" + coordsList +
                '}';
    }
}

以上为生成步骤
下面为校验步骤,chineseErrorSize为误差范围,用户点击验证码必然有误差,不可能百分之百正确。

@Override
    public Boolean checkXYZ(String token, List<Coords> userCoords, int chineseErrorSize) throws IOException {
        List<String> coords = loginService.getXYZList(token);
        System.out.println("coords:" + coords);
        if (logger.isDebugEnabled()) {
            logger.debug("登陆的Coords:" + coords);
        }
        List<Coords> redisCoords = new ArrayList<>();
        for (int i = 0; i < coords.size(); i++) {
            String coordString = coords.get(i);
            Coords coord = JsonUtil.getBean(coordString, Coords.class);
            redisCoords.add(coord);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("redis内Coords:" + redisCoords);
        }
        if (!(redisCoords.size() == userCoords.size())) {
            return false;
        }
        for (int i = 0; i < redisCoords.size(); i++) {
            Coords redis = redisCoords.get(i);
            Coords user = userCoords.get(i);
            if (!(user.getX() < redis.getX() + chineseErrorSize && user.getX() > redis.getX() - chineseErrorSize
                    && redis.getY() + chineseErrorSize > user.getY() && redis.getY() - chineseErrorSize < user.getY())) {
                return false;
            }
        }
        return true;
    }
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
获取图片验证码的值分为两个步骤: 1. 识别验证码图片中的字符 2. 将字符组合成验证码的值 下面是基于Java和OpenCV实现的简单示例代码: ```java import org.opencv.core.*; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; public class CaptchaRecognition { public static void main(String[] args) { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // 读取验证码图片 Mat captcha = Imgcodecs.imread("captcha.png", Imgcodecs.IMREAD_GRAYSCALE); // 对图片进行二值化处理,将字符部分变为黑色,背景部分变为白色 Imgproc.threshold(captcha, captcha, 0, 255, Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU); // 对图片进行腐蚀操作,使字符之间的间隙更明显 Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3)); Imgproc.erode(captcha, captcha, kernel); // 查找图片中的轮廓 Mat hierarchy = new Mat(); Mat contours = captcha.clone(); Imgproc.findContours(contours, new ArrayList<Mat>(), hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE); // 遍历轮廓,获取字符的位置和大小 ArrayList<Rect> charRects = new ArrayList<>(); for (int i = 0; i < contours.rows(); i++) { Rect rect = Imgproc.boundingRect(contours.row(i)); if (rect.width >= 5 && rect.height >= 10 && rect.width * rect.height < captcha.size().area() / 5) { charRects.add(rect); } } // 将字符按照从左到右的顺序排序 charRects.sort((o1, o2) -> Integer.compare(o1.x, o2.x)); // 识别字符 StringBuilder captchaValue = new StringBuilder(); for (Rect rect : charRects) { Mat charImage = captcha.submat(rect); // TODO: 使用OCR技术对charImage进行识别,将识别结果添加到captchaValue中 } // 输出验证码值 System.out.println(captchaValue.toString()); } } ``` 其中,使用OCR技术对字符进行识别需要使用第三方库或API,例如Tesseract OCR、百度OCR等。具体使用方法可以参考官方文档或相关资料。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值