还在手撸验证码Spring Boot 从自定义实现到 Hutool一篇带你彻底搞定验证码这件“小事”其实一点也不小在登录、注册、找回密码这些看似普通的业务场景里验证码往往是系统安全的第一道防线。如果没有它爬虫、撞库脚本、暴力请求可以轻松把你的接口打穿但如果验证码实现得不好又会带来代码冗余、维护困难、样式僵硬等一系列工程问题。很多项目都会经历这样一个过程一开始“不就是个验证码吗我自己画”后来干扰线、字体、随机数、Session、缓存控制……越写越多最终发现成熟工具库早就帮你把坑填平了这篇文章就围绕一个核心问题展开Spring Boot 项目里验证码到底该怎么实现才是又稳又省事我们会完整对比两种方案完全自定义实现验证码生成逻辑适合高度定制化需求基于 Hutool 快速集成多种验证码类型绝大多数项目的最优解为什么图形验证码几乎不可或缺验证码的本质不是“折磨用户”而是区分人和程序。在以下场景中它几乎是必选项登录接口防止暴力破解注册接口防止批量刷号密码重置防止恶意撞库重要操作前的人机确认通过随机字符 干扰元素的方式验证码能显著提高脚本攻击成本是最基础、也是最有效的安全措施之一。实现方式上常见只有两条路自己写一套验证码生成逻辑直接集成成熟工具库下面我们一条一条拆。方案一完全手写一个验证码工具类如果你的项目对验证码样式、字体、干扰规则有非常强的定制需求那么手写一套逻辑是不可避免的。验证码工具类设计思路我们需要完成几件事随机生成验证码字符绘制背景绘制干扰线绘制字符将验证码结果存入 Session将图片输出到响应流项目目录结构src/main/java └── com └── icoderoad └── common └── util └── CaptchaCodeUtil.java自定义验证码工具类实现packagecom.icoderoad.common.util;importjavax.imageio.ImageIO;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjava.awt.*;importjava.awt.image.BufferedImage;importjava.util.Random;publicclassCaptchaCodeUtil{publicstaticfinalStringSESSION_KEYCAPTCHA_CODE;privatefinalRandomrandomnewRandom();privatefinalStringcodeSource0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ;privateintwidth80;privateintheight26;privateintcodeLength4;privateintlineCount40;privateFontgetFont(){returnnewFont(Fixedsys,Font.PLAIN,18);}privateColorrandomColor(intmin,intmax){minMath.min(min,255);maxMath.min(max,255);intrminrandom.nextInt(max-min);intgminrandom.nextInt(max-min);intbminrandom.nextInt(max-min);returnnewColor(r,g,b);}privatevoiddrawLine(Graphicsg){intxrandom.nextInt(width);intyrandom.nextInt(height);intxlrandom.nextInt(12);intylrandom.nextInt(12);g.drawLine(x,y,xxl,yyl);}privateStringdrawChar(Graphicsg,intindex){g.setFont(getFont());g.setColor(randomColor(50,160));StringchString.valueOf(codeSource.charAt(random.nextInt(codeSource.length())));g.drawString(ch,15*index,18);returnch;}publicvoidgenerate(HttpServletRequestrequest,HttpServletResponseresponse){HttpSessionsessionrequest.getSession();BufferedImageimagenewBufferedImage(width,height,BufferedImage.TYPE_INT_RGB);Graphicsgimage.getGraphics();g.setColor(Color.WHITE);g.fillRect(0,0,width,height);for(inti0;ilineCount;i){g.setColor(randomColor(120,200));drawLine(g);}StringBuildercodenewStringBuilder();for(inti1;icodeLength;i){code.append(drawChar(g,i));}session.setAttribute(SESSION_KEY,code.toString());g.dispose();try{ImageIO.write(image,JPEG,response.getOutputStream());}catch(Exceptione){thrownewRuntimeException(生成验证码失败,e);}}}Controller 中调用验证码工具类packagecom.icoderoad.web.controller;importcom.icoderoad.common.util.CaptchaCodeUtil;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;RestControllerpublicclassCaptchaController{GetMapping(/captcha/custom)publicvoidcustomCaptcha(HttpServletRequestrequest,HttpServletResponseresponse){response.setContentType(image/jpeg);response.setHeader(Cache-Control,no-cache);response.setHeader(Pragma,no-cache);response.setDateHeader(Expires,0);newCaptchaCodeUtil().generate(request,response);}}⚠️这种方式完全可控但维护成本高、扩展困难方案二使用 Hutool 快速集成验证码强烈推荐如果你不想把时间浪费在“重复造轮子”上Hutool 基本就是最优解。引入依赖dependencygroupIdcn.hutool/groupIdartifactIdhutool-captcha/artifactIdversion5.8.6/version/dependencyHutool 四种验证码实战示例统一 Controller 路径示例com/icoderoad/web/controller/HutoolCaptchaController.java1. 线条干扰验证码LineCaptchaGetMapping(/captcha/line)publicvoidline(HttpServletResponseresponse)throwsIOException{LineCaptchacaptchaCaptchaUtil.createLineCaptcha(130,38,5,5);response.setContentType(image/jpeg);response.setHeader(Cache-Control,no-cache);captcha.write(response.getOutputStream());}2. 圆圈干扰验证码CircleCaptchaGetMapping(/captcha/circle)publicvoidcircle(HttpServletResponseresponse)throwsIOException{CircleCaptchacaptchaCaptchaUtil.createCircleCaptcha(130,38,5,20);response.setContentType(image/jpeg);response.setHeader(Cache-Control,no-cache);captcha.write(response.getOutputStream());}3. 扭曲验证码ShearCaptchaGetMapping(/captcha/shear)publicvoidshear(HttpServletResponseresponse)throwsIOException{ShearCaptchacaptchaCaptchaUtil.createShearCaptcha(130,38,5,5);response.setContentType(image/jpeg);response.setHeader(Cache-Control,no-cache);captcha.write(response.getOutputStream());}4. GIF 动态验证码GifCaptchaGetMapping(/captcha/gif)publicvoidgif(HttpServletResponseresponse)throwsIOException{GifCaptchacaptchaCaptchaUtil.createGifCaptcha(130,38,5);response.setContentType(image/jpeg);response.setHeader(Cache-Control,no-cache);captcha.write(response.getOutputStream());}自定义验证码内容数字 / 算术纯数字验证码GetMapping(/captcha/number)publicvoidnumber(HttpServletResponseresponse)throwsIOException{RandomGeneratorgeneratornewRandomGenerator(0123456789,4);LineCaptchacaptchaCaptchaUtil.createLineCaptcha(200,80);captcha.setGenerator(generator);captcha.createCode();captcha.write(response.getOutputStream());}算术验证码推荐GetMapping(/captcha/math)publicvoidmath(HttpServletResponseresponse,HttpSessionsession)throwsIOException{ShearCaptchacaptchaCaptchaUtil.createShearCaptcha(200,45,4,4);captcha.setGenerator(newMathGenerator());captcha.createCode();session.setAttribute(MATH_CODE,captcha.getCode());captcha.write(response.getOutputStream());}接口测试建议Apifox使用 GET 请求访问验证码接口响应应为图片而非乱码多次刷新验证码应变化若不变化优先检查缓存头设置关键实践建议验证码值必须存 Session / Redis验证成功后立即清除验证码接口应限流高安全场景可叠加短信 / IP 风控结语验证码不是炫技而是工程取舍需要极致定制→ 自定义实现追求效率与稳定→ Hutool 一步到位在真实项目里99% 的情况你都不需要自己画验证码。把精力留给真正有价值的业务逻辑才是成熟工程师的选择。