导读:本期聚焦于创作的《Spring Boot如何整合Kaptcha实现图形验证码功能?》,敬请观看详情。登录注册页面缺少验证码,接口很容易被脚本恶意刷请求,这是不少后端项目都会遇到的安全隐患。本文围绕Spring Boot整合Kaptcha验证码生成库展开,先介绍Kaptcha的工作原理和常用配置项,再通过完整的代码示例演示如何生成验证码图片、如何把验证码文本存入Session或Redis、如何在前端页面展示和点击刷新,最后封装一个统一的校验接口,并给出区分大小写、过期时间、一次性使用等进阶实践建议,帮助你快速给项目加上一道实用的防护屏障。

图形验证码是最常见的人机校验手段之一,几乎所有涉及登录、注册、找回密码的页面都能看到它的身影。对于Java技术栈的项目来说,Kaptcha是一个非常成熟的验证码生成库,它基于SimpleCaptcha扩展而来,配置灵活、生成的图片干扰性强,与Spring Boot整合也非常顺手。本文将从依赖引入、核心配置、接口开发到前端展示,完整演示一次整合过程,并补充Session存储、防重放校验等实际项目中必须注意的细节。

Spring Boot如何整合Kaptcha实现图形验证码功能?

一、Kaptcha的工作原理与依赖引入

Kaptcha的核心思路是在服务端随机生成一串字符文本,然后通过Java的Graphics2D把这段文本绘制成一张带有噪点、干扰线、扭曲效果的图片,最后把图片以二进制流的形式写回给浏览器。与此同时,正确答案会保存在服务端(通常是Session),等用户提交表单时再与用户输入的值比对。

整个过程对客户端来说只暴露了图片,验证逻辑全部在服务端完成,安全性有基本保障。Kaptcha支持定制的内容相当丰富,包括验证码字符集合、图片边框、字体大小、干扰线数量、背景颜色渐变等,这些都可以通过配置项或代码方式控制。

在Spring Boot项目中引入依赖非常简单,如果项目使用Spring Boot 3.x,需要注意选择Kaptcha 2.3.2及以上版本,并且该依赖托管在谷歌仓库,建议直接使用下面这份坐标:

<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

这份依赖是社区维护的版本,兼容性较好, javax.servlet 和 jakarta.servlet 环境都能通过适配解决。如果你的项目还是Spring Boot 2.x,引入后基本开箱即用,不需要额外处理。

二、配置验证码生成器并暴露图片接口

Kaptcha提供了一个Producer接口,我们只需要在配置类中声明一个Bean,Spring容器启动时就会自动创建验证码生成器。下面是常见的配置方式,把各项参数都做了中文注释:

@Configuration
public class KaptchaConfig {

    @Bean
    public Producer captchaProducer() {
        Properties props = new Properties();
        // 验证码字符范围:去掉了容易混淆的字母和数字
        props.setProperty("kaptcha.textproducer.char.string", "23456789ABCDEFGHJKMNPQRSTUVWXYZ");
        // 验证码长度
        props.setProperty("kaptcha.textproducer.char.length", "4");
        // 图片宽度
        props.setProperty("kaptcha.image.width", "120");
        // 图片高度
        props.setProperty("kaptcha.image.height", "40");
        // 字体大小
        props.setProperty("kaptcha.textproducer.font.size", "32");
        // 干扰线颜色
        props.setProperty("kaptcha.noise.color", "blue");
        // 干扰实现类
        props.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.DefaultNoise");
        // 背景渐变起止色
        props.setProperty("kaptcha.background.clear.from", "240,240,240");
        props.setProperty("kaptcha.background.clear.to", "255,255,255");

        Config config = new Config(props);
        return new DefaultKaptcha(config);
    }
}

配置里的字符集合特意去掉了数字1、字母I、字母O和数字0这类肉眼难分的字符,这是提升用户体验的小技巧,能明显降低用户输错的概率。

接下来编写生成图片的Controller接口。这里有一个关键点:生成验证码后必须立刻调用createText拿到文本并存入Session,然后再用createImage生成图片,两次调用的文本是同一个值,千万不要先建图再取文本。

@RestController
@RequestMapping("/captcha")
public class CaptchaController {

    @Autowired
    private Producer captchaProducer;

    @GetMapping("/image")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 先生成文本,存入Session
        String text = captchaProducer.createText();
        request.getSession().setAttribute("captchaCode", text);
        // 设置响应头,禁止浏览器缓存
        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setContentType("image/jpeg");
        // 生成图片并写回
        BufferedImage image = captchaProducer.createImage(text);
        try (ServletOutputStream out = response.getOutputStream()) {
            ImageIO.write(image, "jpg", out);
        }
    }
}

接口没有返回JSON,而是直接把图片流写回响应体,前端只需把img标签的src指向这个接口地址即可。响应头里一定要加上禁止缓存的设置,否则部分浏览器会复用旧图片,导致用户看到的图和Session里的答案对不上。

三、前端展示与校验接口封装

前端部分几乎不需要额外代码,用一个img标签加载验证码,再绑定点击事件刷新即可。刷新时给URL拼上一个随机参数,强制浏览器重新请求:

<div class="captcha-box">
    <input type="text" id="captchaInput" placeholder="请输入验证码">
    <img id="captchaImg" src="/captcha/image" onclick="refreshCaptcha()" title="点击刷新">
</div>
<script>
function refreshCaptcha() {
    document.getElementById('captchaImg').src = '/captcha/image?t=' + new Date().getTime();
}
</script>

服务端的校验接口建议单独封装,登录请求进来后先校验验证码,通过再走账号密码逻辑。校验时注意三点:忽略大小写、校验后立即清除Session中的值、验证码为空时也返回失败。下面是一个可复用的工具方法:

@Service
public class CaptchaService {

    public boolean verify(HttpServletRequest request, String input) {
        HttpSession session = request.getSession();
        String expected = (String) session.getAttribute("captchaCode");
        // 无论校验成功与否,一次性使用,防止重放
        session.removeAttribute("captchaCode");
        if (expected == null || input == null || input.trim().isEmpty()) {
            return false;
        }
        return expected.equalsIgnoreCase(input.trim());
    }
}

一次性消费是这段代码最重要的设计。如果校验失败后不清除Session里的值,攻击者就可以拿到一个正确验证码后反复请求接口,验证码就形同虚设了。

四、集群环境下的进阶实践

上面的方案把验证码存在Session里,单机部署没有问题。但一旦项目部署多台机器并使用Nginx做负载均衡,用户请求生成验证码和提交登录可能落在不同节点上,Session不共享就会导致校验必然失败。

解决这个问题有两条路。一是引入Spring Session加Redis,让所有节点共享Session,改动成本最低;二是绕开Session,自己生成一个UUID作为凭证,把验证码文本和UUID一起写入Redis并设置过期时间,前端拿到UUID后随表单一起提交。第二种方式对前后端分离架构更友好,参考实现如下:

@Autowired
private StringRedisTemplate redisTemplate;

@GetMapping("/captcha/v2")
public Map<String, String> captchaV2(HttpServletResponse response) throws IOException {
    String text = captchaProducer.createText();
    String uuid = UUID.randomUUID().toString();
    // 存入Redis,两分钟后自动过期
    redisTemplate.opsForValue().set("captcha:" + uuid, text, 2, TimeUnit.MINUTES);
    BufferedImage image = captchaProducer.createImage(text);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ImageIO.write(image, "jpg", bos);
    String base64 = Base64.getEncoder().encodeToString(bos.toByteArray());
    Map<String, String> result = new HashMap<>();
    result.put("uuid", uuid);
    result.put("image", "data:image/jpeg;base64," + base64);
    return result;
}

这个版本直接返回Base64编码的图片,前端用img标签的src直接展示返回的data URI即可,省去了静态资源请求。校验时通过Redis的get和delete原子操作取出并删除,同样保证一次性使用。设置2分钟过期时间也很有必要,既能给用户留够输入时间,又能缩短验证码被暴力尝试的窗口期。

另外从安全角度补充一点:图形验证码只能挡住最基础的脚本攻击,如果业务对安全要求更高,可以在此基础上叠加接口限流、错误次数锁定、行为验证码等手段,多层防御才能构建真正可靠的防护体系。

Spring BootKaptcha图形验证码修改时间:2026-09-03 09:39:01

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260903/49481.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。