开源免费!传统项目也可以接入天爱验证码(TAC),坑我来填

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

行为验证码已经慢慢变成了开发验证的必须行为,之前分享过TIANAI-CAPTCHA天爱验证码的SpringBoot用法,非常简单。

但是,这两天需要在传统的SpringMVC项目中接入,发现使用并不简单。看看我都踩了那些坑?

02 天爱验证码简介

还是简单介绍一下tianai-captcha项目。TIANAI-CAPTCHA下简称TAC 是一个开源的行为验证码工具,支持多种验证码类型,分别有javago等语言的实现。开源版默认提供了 滑块验证码、旋转验证码、文字点选验证码、滑动还原验证码等。

后端的接入方式主要有两种:

  • SpringBoot项目
  • 传统项目

Gitee地址:gitee.com/dromara/tia...

在线体验地址:captcha.tianai.cloud/

在线文档:doc.captcha.tianai.cloud/

03 传统项目接入

SpringBoot项目接入太简单了,包括二次验证,配置一下参数即可完成。而传统项目的接入,部分功能需要自行实现。

在众多验证码类型中,我选择了常用的滑动验证码,并自定义图片。

3.1 依赖引入

xml 复制代码
<dependency>
    <groupId>cloud.tianai.captcha</groupId>
    <artifactId>tianai-captcha</artifactId>
    <version>1.5.5</version>
</dependency>

3.2 创建验证码对象

由于线程安全的缘故,官方建议ImageCaptchaApplication创建一次。

这里创建了一个ImageCaptchaProxy用来管理ImageCaptchaApplication,以保证创建一次。

java 复制代码
public class ImageCaptchaProxy {

    private ImageCaptchaApplication imageCaptchaApplication;

    @Autowired
    private CacheService cacheService;

    @PostConstruct
    public void initialize() {
        imageCaptchaApplication = TACBuilder.builder()
                // 设置资源存储器,默认是 LocalMemoryResourceStore
                .setResourceStore(new LocalMemoryResourceStore())
                .setCacheStore(new RedisCacheStore(cacheService))
                // 加载系统自带的默认资源(系统内置了几个滑块验证码缺口模板图,调用此函数加载)
                .addDefaultTemplate()
                // 设置验证码过期时间, 单位毫秒, default 是默认验证码过期时间,当前设置为10秒,
                // 可以自定义某些验证码类型单独的过期时间, 比如把点选验证码的过期时间设置为60秒
                .expire("default", 10000L)
                // 设置拦截器,默认是 EmptyCaptchaInterceptor.INSTANCE
                .setInterceptor(EmptyCaptchaInterceptor.INSTANCE)
                // 添加验证码背景图片
                // arg1 验证码类型(SLIDER、WORD_IMAGE_CLICK、ROTATE、CONCAT),
                // arg2 验证码背景图片资源
                // 背景图宽高为 600x360
                .addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "META-INF/cut-image/1.jpg"))
                .addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "META-INF/cut-image/2.jpg"))
                // 图片转换器,默认是将图片转换成base64格式, 背景图为jpg, 模板图为png, 如果想要扩展,可替换成自己实现的
                .setTransform(new Base64ImageTransform())
                .build();
    }

    /**
     * @Description: 获取验证码信息
     *
     * @Author: ws
     * @Date: 2026/3/12 10:34
     **/
    public ApiResponse<ImageCaptchaVO> getImageCaptcha() {
        return imageCaptchaApplication.generateCaptcha(CaptchaTypeConstant.SLIDER);
    }
    
    /**
     * @Description: 验证验证码信息
     *
     * @Author: ws
     * @Date: 2026/3/12 10:34
     **/
    public ApiResponse verifyImageCaptcha(String captchaId, ImageCaptchaTrack track) {
        return imageCaptchaApplication.matching(captchaId, track);
    }
}

这里对象的创建大家可以根据自己的实际情况删减配置项,这里有几个配置项非常重要,是官方保留的扩展。

setResourceStore()

这里设置资源存储器,默认是 LocalMemoryResourceStore。也就是将资源也就是滑动的图片信息,放在内存中的。

直接使用内存没有问题,直接存储在本地缓存。这里也可以统一放在其他介质中,如RedisSpringBoot版官方提供了RedisResourceStore的类,但是传统的项目并没有,如果想要放在缓存中统一使用,就需要实现CrudResourceStore接口即可。直接将SpringBoot版中的复制过来,改成自己的Redis客户端即可。

这个参数设置不设置不重要,就看你资源要不要共享。

setCacheStore()

用来存储被切割的背景图以及拼接的碎片,功能上主要用来验证码的验证。这个非常重要,默认还是内存,在单节点项目中没有问题,一旦涉及多节点,必然验证不通过。

这是我踩的最大的坑。

多节点的环境中,需要通过保存在其他介质中,如Redis。需要实现CacheStore接口,直接将LocalCacheStore复制过来,换成Redis客户单即可。

这里使用构造函数将Redis客户单传递进来。

java 复制代码
public class RedisCacheStore implements CacheStore {

    private CacheService cacheService;

    public RedisCacheStore(CacheService cacheService) {
        this.cacheService = cacheService;
    }

    @Override
    public AnyMap getCache(String key) {
        String json = cacheService.getString(key);
        return JSON.parseObject(json, AnyMap.class);
    }

    @Override
    public AnyMap getAndRemoveCache(String key) {
        AnyMap anyMap = getCache(key);
        cacheService.delete(key);
        return anyMap;
    }

    @Override
    public boolean setCache(String key, AnyMap data, Long expire, TimeUnit timeUnit) {
        cacheService.delete(key);
        cacheService.setExpireSecV2(new BaseExpireSec(key, JSON.toJSONString(data), (int) timeUnit.toSeconds(expire)));
        return true;
    }

    @Override
    public Long incr(String key, long delta, Long expire, TimeUnit timeUnit) {
        Map<String, Object> value = getAndRemoveCache(key);
        if (value != null) {
            Long incr = (Long) value.get("___incr___");
            if (incr == null) {
                incr = 0L;
            }
            incr += delta;
            setCache(key, AnyMap.of(Collections.singletonMap("___incr___", incr)), expire, timeUnit);
            return incr;
        }
        setCache(key, AnyMap.of(Collections.singletonMap("___incr___", delta)), expire, timeUnit);
        return delta;
    }

    @Override
    public Long getLong(String key) {
        Map<String, Object> stringObjectMap = getCache(key);
        if (stringObjectMap != null) {
            return (Long) stringObjectMap.get("___incr___");
        }
        return null;
    }

    @Override
    public void close() throws Exception {

    }

addResource()

添加验证码背景图片,也就是自定义图片。滑动验证码使用CaptchaTypeConstant.SLIDER类型即可,类型后面跟图片的具体位置。这里的图片背景的宽高必须为 600x360,否则无法加载。

支持类型:

新增方法

为了对外不暴露imageCaptchaApplication,我特意增加了获取验证码和验证验证码的方法。

  • getImageCaptcha()
  • verifyImageCaptcha()

3.3 前端页面

传统项目使用的是JSP技术,只能通过引入js的方式接入。

官网上说,可以使用load.js,我使用之后没有效果。这是踩的第二个坑。

没有使用load.js直接中https://gitee.com/tianai/tianai-captcha-demo静态资源,同时需要修改页面初始化的方法。

tianai-captcha-demo需要复制项目可以直接访问的文件下。

我直接放在了webapp

然后引入项目

初始化触发

js 复制代码
new TAC({
        // 生成接口
        requestCaptchaDataUrl: "${ctx}/captcha/showImageCaptcha",
        // 验证接口
        validCaptchaUrl: "${ctx}/captcha/verifyImageCaptcha",
        // 验证码绑定的div块 (必选项,必须配置)
        bindEl: "#captcha-box",
        // 验证成功回调函数(必选项,必须配置)
        validSuccess: (res, c, t) => {
            console.log("验证码验证成功回调...");
            // 销毁验证码组件
            t.destroyWindow();
            $.LAYER.close();

            // 调用具体的业务方法
            loginPreCheck(res.data.token);
        },
        // 刷新按钮回调事件
        btnRefreshFun: (el, tac) => {
            console.log("刷新按钮触发事件...");
            tac.reloadCaptcha();
        },
        // 关闭按钮回调事件
        btnCloseFun: (el, tac) => {
            console.log("关闭按钮触发事件...");
            tac.destroyWindow();
            $.LAYER.close();
        }
    }, {
        // 一些样式配置
        logoUrl: "/images/logo.png",
        btnUrl: "/images/btn.png"
    }).init();

代码中$.LAYER.close()是为了关闭遮罩效果,公司里面的小插件可以忽略。

这里要说的样式的配置。配置项如图,搭建可以自由选择,我这里配置logo和按钮的图片

3.4 服务端

服务端比较简单,就是提供showImageCaptcha()verifyImageCaptcha()即可。

java 复制代码
@Controller
@RequestMapping("/captcha")
public class ImageCaptchaController {

    @Autowired
    private ImageCaptchaProxy imageCaptchaProxy;
    @Autowired
    private CacheService cacheService;

    /**
     * @Description: 显示图片验证码
     *
     * @Author: ws
     * @Date: 2026/3/12 10:09
     **/
    @RequestMapping("/showImageCaptcha")
    @ResponseBody
    public ApiResponse<ImageCaptchaVO> showImageCaptcha() {
        return imageCaptchaProxy.getImageCaptcha();
    }

    /**
     * @Description: 验证验证码
     *
     * @Author: ws
     * @Date: 2026/3/12 10:32
     **/
    @RequestMapping("/verifyImageCaptcha")
    @ResponseBody
    public ApiResponse verifyImageCaptcha(@RequestBody ImageCaptchaDto imageCaptchaDto, HttpServletRequest request) {
        ApiResponse apiResponse = imageCaptchaProxy.verifyImageCaptcha(imageCaptchaDto.getId(), imageCaptchaDto.getData());
        if (apiResponse.isSuccess()) {
            // 生成唯一的token
            String token = UUIDUtil.getUUID();
            cacheService.sadd(AccountConstant.VERIFY_CODE_TOKEN, token);
            cacheService.setDateSecJustKey(AccountConstant.VERIFY_CODE_TOKEN, DateUtils.getDateEnd(new Date()));

            Map<String, Object> map = new HashMap<>();
            map.put("token", token);
            apiResponse.setData(map);
        }
        return apiResponse;
    }
}

这里需要注意的有两点:

  • 验证参数自定义
  • 二次验证

验证参数自定义

verifyImageCaptcha方法接受参数是我们自定义的。官方也给出了Demo

自定义参数并使用@RequestBody接受,说明前端数据是一个json数据。

二次验证

校验过程我们会发现和业务没有任何关系,如登录系统。验证通过才会调用登录接口,如果别人直接调用登录接口绕过页面,那么行为验证码就形同虚设,也是等保所不能容忍的。

所以我们需要在登录后再次验证。实现也很简单,验证通过之后,生成唯一标识token并存入缓存,只要登录的时候携带此token,服务端验证缓存中是否存在即可。

04 小结

单体项目测试的时候总会发现一切完好,但是到了分布式环境、多节点环境总会出现数据丢失的问题,会让框架的使用变的复杂。理解其原理,修修补补,马上完好如初!

相关推荐
京东云开发者2 小时前
智算监控的下半场:从基础设施报警到算力精算师
程序员
xxxxxxllllllshi2 小时前
java值传递和引用传递的区别?举例一些常见都笔试面试题说明,最后有速记口诀
java·开发语言
猹叉叉(学习版)2 小时前
【ASP.NET CORE】 13. DDD初步实现
笔记·后端·架构·c#·asp.net·.netcore
huabiangaozhi2 小时前
Spring Cloud Gateway 整合Spring Security
java·后端·spring
小王不爱笑1322 小时前
Java List 集合全面解析:ArrayList、LinkedList 与 Vector 的深度对比
java·windows·list
KIKIiiiiiiii2 小时前
微信自动化机器人开发
java·开发语言·人工智能·python·微信·自动化
victory04312 小时前
containerd打包命令 和NFS挂载
java·开发语言
野犬寒鸦2 小时前
从零起步学习计算机操作系统:进程篇(知识扩展提升)
java·服务器·开发语言·后端·面试