当前位置: 代码网 > it编程>编程语言>Java > Spring框架实现滑动验证码功能的代码示例

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

2024年07月28日 Java 我要评论
1. 整体描述之前项目需要在验证码模块,增加滑动验证码,用来给手机端使用的,大概看了下,主要方法就是将图片切割,然后记住偏移量,进行滑动,前端验证的时候,需要用前端传入的偏移量和生成的偏移量进行对比,

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;
    /**
     * 画布宽度
     **/
    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.base64;
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有颜色
        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 "验证码已失效";
        }
        // 根据移动距离判断验证是否成功
        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.requestbody;
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滑动验证码功能的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com