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实现
在这里插入图片描述

在这里插入图片描述

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值