Ubuntu 18.04深度学习驱动安装避坑指南:NVIDIA驱动与CUDA兼容性实战
2026/6/16 13:49:55
验证码这件“小事”,其实一点也不小
在登录、注册、找回密码这些看似普通的业务场景里,验证码往往是系统安全的第一道防线。如果没有它,爬虫、撞库脚本、暴力请求可以轻松把你的接口打穿;但如果验证码实现得不好,又会带来代码冗余、维护困难、样式僵硬等一系列工程问题。
很多项目都会经历这样一个过程:
这篇文章就围绕一个核心问题展开:Spring Boot 项目里,验证码到底该怎么实现,才是又稳又省事?
我们会完整对比两种方案:
验证码的本质不是“折磨用户”,而是区分人和程序。
在以下场景中,它几乎是必选项:
通过随机字符 + 干扰元素的方式,验证码能显著提高脚本攻击成本,是最基础、也是最有效的安全措施之一。
实现方式上,常见只有两条路:
下面我们一条一条拆。
如果你的项目对验证码样式、字体、干扰规则有非常强的定制需求,那么手写一套逻辑是不可避免的。
我们需要完成几件事:
src/main/java └── com └── icoderoad └── common └── util └── CaptchaCodeUtil.javapackagecom.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_KEY="CAPTCHA_CODE";privatefinalRandomrandom=newRandom();privatefinalStringcodeSource="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";privateintwidth=80;privateintheight=26;privateintcodeLength=4;privateintlineCount=40;privateFontgetFont(){returnnewFont("Fixedsys",Font.PLAIN,18);}privateColorrandomColor(intmin,intmax){min=Math.min(min,255);max=Math.min(max,255);intr=min+random.nextInt(max-min);intg=min+random.nextInt(max-min);intb=min+random.nextInt(max-min);returnnewColor(r,g,b);}privatevoiddrawLine(Graphicsg){intx=random.nextInt(width);inty=random.nextInt(height);intxl=random.nextInt(12);intyl=random.nextInt(12);g.drawLine(x,y,x+xl,y+yl);}privateStringdrawChar(Graphicsg,intindex){g.setFont(getFont());g.setColor(randomColor(50,160));Stringch=String.valueOf(codeSource.charAt(random.nextInt(codeSource.length())));g.drawString(ch,15*index,18);returnch;}publicvoidgenerate(HttpServletRequestrequest,HttpServletResponseresponse){HttpSessionsession=request.getSession();BufferedImageimage=newBufferedImage(width,height,BufferedImage.TYPE_INT_RGB);Graphicsg=image.getGraphics();g.setColor(Color.WHITE);g.fillRect(0,0,width,height);for(inti=0;i<lineCount;i++){g.setColor(randomColor(120,200));drawLine(g);}StringBuildercode=newStringBuilder();for(inti=1;i<=codeLength;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);}}}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 基本就是最优解。
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-captcha</artifactId><version>5.8.6</version></dependency>统一 Controller 路径示例:com/icoderoad/web/controller/HutoolCaptchaController.java
@GetMapping("/captcha/line")publicvoidline(HttpServletResponseresponse)throwsIOException{LineCaptchacaptcha=CaptchaUtil.createLineCaptcha(130,38,5,5);response.setContentType("image/jpeg");response.setHeader("Cache-Control","no-cache");captcha.write(response.getOutputStream());}@GetMapping("/captcha/circle")publicvoidcircle(HttpServletResponseresponse)throwsIOException{CircleCaptchacaptcha=CaptchaUtil.createCircleCaptcha(130,38,5,20);response.setContentType("image/jpeg");response.setHeader("Cache-Control","no-cache");captcha.write(response.getOutputStream());}@GetMapping("/captcha/shear")publicvoidshear(HttpServletResponseresponse)throwsIOException{ShearCaptchacaptcha=CaptchaUtil.createShearCaptcha(130,38,5,5);response.setContentType("image/jpeg");response.setHeader("Cache-Control","no-cache");captcha.write(response.getOutputStream());}@GetMapping("/captcha/gif")publicvoidgif(HttpServletResponseresponse)throwsIOException{GifCaptchacaptcha=CaptchaUtil.createGifCaptcha(130,38,5);response.setContentType("image/jpeg");response.setHeader("Cache-Control","no-cache");captcha.write(response.getOutputStream());}@GetMapping("/captcha/number")publicvoidnumber(HttpServletResponseresponse)throwsIOException{RandomGeneratorgenerator=newRandomGenerator("0123456789",4);LineCaptchacaptcha=CaptchaUtil.createLineCaptcha(200,80);captcha.setGenerator(generator);captcha.createCode();captcha.write(response.getOutputStream());}@GetMapping("/captcha/math")publicvoidmath(HttpServletResponseresponse,HttpSessionsession)throwsIOException{ShearCaptchacaptcha=CaptchaUtil.createShearCaptcha(200,45,4,4);captcha.setGenerator(newMathGenerator());captcha.createCode();session.setAttribute("MATH_CODE",captcha.getCode());captcha.write(response.getOutputStream());}在真实项目里,99% 的情况你都不需要自己画验证码。把精力留给真正有价值的业务逻辑,才是成熟工程师的选择。