spring 小红书 旋转圆形验证码,实现方案

2021年实现 Java Vue uni-app 三端 滑动拼图验证码,两年前参考小红书实现了一版 滑动验证码,代码比较简陋,作为一个测试性的demo来说已经够了。
近期发现小红书更新了验证方式为 圆形旋转验证码,随即实现一波。

  • 2021年实现方案的缺点
    • 每次图形的生成 都是后台读取模板图和背景图,随机位置切图,返回base64图片编码,浪费系统资源。
    • 切图方法仅实现了功能,纯依赖循环坐标覆盖色值, 不够优雅。
新版实现方案
  • 背景图 600px * 400px。剪裁中心区域200px * 200px,4px留白。核心工具类 已提供

  • 创建验证码管理模块,不断更新和改进滑块图像和背景图像增加破解的难度,背景图上传,后端进行切图。

  • 保存至资源服务器。仅返回图片的资源访问地址。

  • 前端UI设置参考 小红书实现。

效果如下:
在这里插入图片描述

核心切图工具类

依赖 Thumbnails

package io.github.smilexizheng.smileboot.common.utils.captcha;

import lombok.SneakyThrows;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;

/**
 * 旋转图形验证码
 * 抗锯齿化,旋转图形不变形 等等
 * 工具类
 *
 * @author smile
 */
public class CaptchaImageUtil {


    private final static Logger log = LogManager.getLogger(CaptchaImageUtil.class);

    /**
     * 中心区域
     */
    private static final int cropSize = 200;
    /**
     * 描边区域
     */
    private static final int borderSize = 4;


    /**
     * 去图片的中心 为圆形
     *
     * @param is
     * @return
     */
    @SneakyThrows
    public static BufferedImage cutCenterToCircle(InputStream is) {
        BufferedImage originalImage = ImageIO.read(is);

        int x = (originalImage.getWidth() - cropSize) / 2;
        int y = (originalImage.getHeight() - cropSize) / 2;
        BufferedImage croppedImage = Thumbnails.of(originalImage)
                .sourceRegion(x, y, cropSize, cropSize)
                .size(cropSize, cropSize)
                .asBufferedImage();
        BufferedImage resultImage = new BufferedImage(cropSize + borderSize * 2, cropSize + borderSize * 2, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = resultImage.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setColor(Color.WHITE);
        g.fillOval(0, 0, cropSize + borderSize * 2, cropSize + borderSize * 2);
        g.setComposite(AlphaComposite.SrcIn);
        g.setClip(new Ellipse2D.Float(borderSize, borderSize, cropSize, cropSize));
        g.drawImage(croppedImage, borderSize, borderSize, null);
        g.dispose();
        return resultImage;
    }

    /**
     * 图片中心置为为黑色
     *
     * @param is
     * @return
     */
    public static BufferedImage centerToBlack(InputStream is) {
        return centerToColor(is, Color.BLACK, false);
    }

    /**
     * 将图片中心改成任意颜色
     *
     * @param is    流
     * @param color 颜色
     * @param alpha 完全透明
     * @return
     */
    @SneakyThrows
    public  static BufferedImage centerToColor(InputStream is, Color color, boolean alpha) {
        BufferedImage originalImage = ImageIO.read(is);
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();
        int centerX = width / 2;
        int centerY = height / 2;

        BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = resultImage.createGraphics();
        g.drawImage(originalImage, 0, 0, null);

        int x = centerX - cropSize / 2;
        int y = centerY - cropSize / 2;

        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        // 设置抗锯齿渲染
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        if (alpha) {
            // 设置透明度为0,即完全透明
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0));
        } else {
            g.setColor(color);
        }
        g.fillOval(x, y, cropSize, cropSize);
        g.dispose();
        return resultImage;
    }


    /**
     * 旋转图片
     *
     * @param originalImage
     * @param angle
     * @return
     */
    @SneakyThrows
    public static BufferedImage rotate(BufferedImage originalImage, double angle) {

        int width = originalImage.getWidth();
        int height = originalImage.getHeight();

        double radians = Math.toRadians(angle);

        AffineTransform at = new AffineTransform();
        at.rotate(radians, width / 2.0, height / 2.0);

        BufferedImage rotatedImage = new BufferedImage(width, height, originalImage.getType());
        Graphics2D g = rotatedImage.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        // 设置抗锯齿渲染
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.drawImage(originalImage, at, null);
        g.dispose();

        return rotatedImage;
    }

    public  static String toFile(BufferedImage bufferedImage, String fileName) throws Exception {
        Assert.hasText(fileName, "输出路径不可为空!");
        String path = new File(ResourceUtils.getURL("classpath:").getPath()).getAbsolutePath();
        String s = "/static/" + fileName;
        File file = new File(path, s);
        Thumbnails.of(bufferedImage).scale(1).toFile(file);
        return s;
    }
    public  static String toFile(BufferedImage bufferedImage, String fileName, int width, int height) throws Exception {
        Assert.hasText(fileName, "输出路径不可为空!");
        String path = new File(ResourceUtils.getURL("classpath:").getPath()).getAbsolutePath();
        String s = "/static/" + fileName;
        File file = new File(path, s);
        Thumbnails.of(bufferedImage).size(width, height).toFile(file);
        return s;
    }
}

切图效果:
在这里插入图片描述
有了这个核心工具切图,仅需要上传一张600*400背景图,后端循环随机生成 100-360度 多张中心图,都上传至静态服务器,将背景图片地址和角度数据保存到数据库中,项目启动时 将验证码加载到redis缓存中。

剩下的功能实现都比较简单了,具体实现 可以参考如下方案。

1.register 获取验证码,根据rid加密数据

在这里插入图片描述
1.根据请求头 进行MD5加密算法+随机UUID,作为Redis缓存key,返回背景图,旋转图的资源地址

2.check 校验,根据rid加密数据

在这里插入图片描述

1.根据滑动距离+时间,校验平均速度。
2.验证旋转角度。

前端 React实现
在这里插入图片描述

在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
滑动拼图验证码小红书中的应用类似于其他网站。滑动拼图验证码是一种人机验证机制,通过用户拖动滑块将缺口对齐,以证明用户是真实的人类,而不是自动化程序或恶意机器人。 具体到小红书实现,根据引用中提供的代码,可以看到它使用了一个名为`slideverify`的自定义组件。这个组件接受一些参数,如滑块宽度、滑块高度、滑块位置等,并提供了一些回调函数,如`onSuccess`、`onRefresh`等。 其中,`getImageVerifyCode`函数用于获取验证码图片,并将图片的地址赋值给`imgurl`和`miniimgurl`。`imgurl`存储原始大小的验证码图片地址,`miniimgurl`存储缩略图的验证码图片地址。 `onRefresh`函数用于刷新验证码,它会清空`imgurl`和`miniimgurl`的值,并重新调用`getImageVerifyCode`函数获取新的验证码图片。 `onSuccess`函数在滑动结束后,将滑动的距离作为参数传入,并调用`verifyImageCode`函数进行后台验证。根据后台返回的验证结果,如果通过则显示成功信息,否则显示错误信息,并调用`onRefresh`函数刷新验证码。 总的来说,滑动拼图验证码小红书中的实现是通过自定义组件和一些回调函数来完成的,它增加了用户与机器的交互,提高了系统的安全性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Vue实现滑动拼图验证码功能](https://download.csdn.net/download/weixin_38747917/14818686)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [3分钟使用Halcon识别网易滑块拼图验证码](https://blog.csdn.net/qq_29888333/article/details/84192678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java Vue uni-app 三端实现,滑动拼图验证码](https://blog.csdn.net/qq_32698323/article/details/118876646)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值