目录
6.综合性练习
6.1 验证码案例
6.1.1 需求
6.1.2 准备工作
6.1.3 约定前后端交互接口
需求分析
接口定义
1.生成验证码
2.校验验证码是否正确
6.1.4 Hutool工具介绍
6.1.5 实现服务端代码
1.引入依赖
2.实现验证码
测试环节
3.校验验证码
6.1.6 调整前端页面代码
6.1.7 运行测试
小结
7.总结(课件内容)
8.代码分享
书接上文:Java EE 进阶:6.SpringBoot 配置文件(第一弹)~~
6.综合性练习
6.1 验证码案例
随着安全性的要求越来越高, 目前项目中很多都使用了验证码, 验证码的形式也是多种多样, 更复杂的图形验证码和行为验证码已经成为了更流行的趋势.
验证码其实并不难,像上面的验证码就是:验证码=字符串+干扰元素
字符串:0~9、a~z、A~Z、汉字……
干扰元素:x 轴 y 轴 任意两点之间随便连个直线、曲线、圆形……
像下面这些验证码更是简单,搞个图片集,在 x 轴上就可以操作完成👇
验证码的实现方式很多, 可以前端实现, 也可以后端实现. 网上也有比较多的插件或者工具包可以使用,咱们选择使用 Hutool 提供的小工具来实现
而且现在前后端也不怎么去写验证码了,因为现在验证码都是开源的了,人家都写好了,咱们直接拿来调用就行了~~
比如:https://gitee.com/LongbowEnterprise/SliderCaptcha#http://longbowenterprise.gitee.io/slidercaptcha/
我们这里就主要是 Hutool 去弄了:简介 | Hutool
6.1.1 需求
界面如下图所示
1. 页面生成验证码
2. 输入验证码, 点击提交, 验证用户输入验证码是否正确, 正确则进行页面跳转
6.1.2 准备工作
创建项目,引入 SpringMVC 的依赖包,把前端页面放在项目中(课件中提供)
码云地址:JavaEE进阶课程资料包
此时我们运行程序发现页面是这样的👇
6.1.3 约定前后端交互接口
需求分析
后端需要提供两个服务
1. 生成验证码, 并返回验证码
2. 校验验证码是否正确
接口定义
1.生成验证码
请求:
请求URL: /captcha/getCaptcha响应: 验证码图片内容
浏览器给服务器发送一个 /captcha/getCaptcha 这样的请求, 服务器返回一个图片, 浏览器显
示在页面上
2.校验验证码是否正确
请求: /captcha/check
请求URL: /captcha/check 请求参数: captcha=xn8dcaptcha : 用户输入的验证码
响应:true根据用户输入的验证码, 校验验证码是否正确. true: 验证成功. false: 验证失败.
6.1.4 Hutool工具介绍
咱们课程中验证码的实现, 使用Hutool提供的小工具来实现
Hutool是一个Java工具包类库,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类.
Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以"甜甜的".
Hutool官网: Hutool🍬一个功能丰富且易用的Java工具库,涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等功能。
Hutool参考文档: Hutool参考文档
Hutool源码: GitHub - chinabugotech/hutool: 🍬A set of tools that keep Java sweet. · GitHub
6.1.5 实现服务端代码
1.引入依赖
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-captcha</artifactId> <version>5.8.22</version> </dependency>
然后把 hutool-http 改为 hutool-captcha👇
注意:这里一开始没引入版本的时候是报错的,博主这里引入版本号之后又删下去了才不报错~~
接下来需要给它版本号,因为这个并没有被 SpringBoot 集成,所以版本号需要我们去写,但是我们并不知道版本号是啥,这时候就需要去 Maven 仓库中去查了
当然,我们也可以在 API 这里直接看到👇
可以看到版本号是 5.8.48👇
加入版本号,依赖就引入完成了👇
2.实现验证码
测试环节
直接使用它的代码进行测试👇
根据API生成验证码, 并进行测试
package com.bit.captcha; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.LineCaptcha; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-11-01 * Time: 16:10 */ public class CaptchaTest { public static void main(String[] args) { //定义图形验证码的长和宽 LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); //图形验证码写出,可以写出到文件,也可以写出到流 lineCaptcha.write("d:/line.png"); } }运行成功👇
这时我们根据它的输出路径,在 D 盘中就能找到刚刚生成的图片👇
这就生成完毕了,用起来非常简单~~
当然,我们也可以获取到它的 code👇
我们改为 打印👇
public class CaptchaTest { public static void main(String[] args) { //定义图形验证码的长和宽 LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); //图形验证码写出,可以写出到文件,也可以写出到流 lineCaptcha.write("d:/line.png"); //输出code System.out.println(lineCaptcha.getCode()); } }再生成一次👇
验证码项目演示-写到浏览器中
上述视频中生成的验证码对于前端页面有点大,我们适当调整一下👇
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(100, 50);但是这就属于硬编码了,不太方便,我们接下来尝试将其放到配置文件中~~
验证码配置项对应的Java对象👇
package com.bit.captcha.model; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-11-01 * Time: 17:29 */ @ConfigurationProperties(prefix = "captcha") @Configuration @Data public class CaptchaProperties { private Integer width; private Integer height; }配置文件👇
#spring.application.name=spring-captcha-demo #将上面 properties 文件中的默认内容自行转成下面的 spring: application: name: spring-captcha-demo captcha: width: 100 height: 50调整Controller代码👇
package com.bit.captcha.controller; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.ShearCaptcha; import com.bit.captcha.model.CaptchaProperties; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-11-01 * Time: 16:58 */ @RequestMapping("/captcha") @RestController public class CaptchaController { @Autowired//把配置文件的内容注入进来 private CaptchaProperties captchaProperties; @RequestMapping("/getCaptcha") public void getCaptcha(HttpServletResponse response){ try { //生成验证码 ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(captchaProperties.getWidth(),captchaProperties.getHeight()); //此时我们想把生成的验证码写到浏览器中,需要 response captcha.write(response.getOutputStream()); } catch (IOException e) { throw new RuntimeException(e); }//我们此时来运行试试效果~~ } }
3.校验验证码
package com.bit.captcha.controller; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.ShearCaptcha; import com.bit.captcha.model.CaptchaProperties; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-11-01 * Time: 16:58 */ @RequestMapping("/captcha") @RestController public class CaptchaController { @Autowired//把配置文件的内容注入进来 private CaptchaProperties captchaProperties; private String code; @RequestMapping("/getCaptcha") public void getCaptcha(HttpServletResponse response){ try { //生成验证码 ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(captchaProperties.getWidth(),captchaProperties.getHeight()); code=captcha.getCode(); //此时我们想把生成的验证码写到浏览器中,需要 response captcha.write(response.getOutputStream()); } catch (IOException e) { throw new RuntimeException(e); }//我们此时来运行试试效果~~ } @RequestMapping("/check") public boolean check(String captcha){ if(code.equals(captcha)) } }但是写到这里就出现问题了,我们知道 HttpServletResponse 是无状态的,那么在多个用户访问的时候就会出现问题,当张三来访问的时候 code=1234,然后李四也来访问了,此时 code又变成了 5678,此时张三在 check 方法部分输入 1234,就显示错误了~~
当时我们是怎么解决这个问题的??我们为了去保存用户信息,去存储不同用户不同的值,使用的是 session~~
所以我们可以把 code 放到 session 中,代码就变成了👇
code=captcha.getCode(); //存储到session中 session.setAttribute("key",code); //同时也要记录时间,时间过去太久就不校验了,直接返回 false session.setAttribute("date",new Data());那么此时对于 时间 和 code 又是硬编码了,所以我们还是使用配置文件👇
#spring.application.name=spring-captcha-demo #将上面 properties 文件中的默认内容自行转成下面的 spring: application: name: spring-captcha-demo captcha: width: 150 height: 50 session: key: CAPTCHA_SESSION_KEY date: CAPTCHA_SESSION_DATE验证码配置项对应的Java对象👇
@ConfigurationProperties(prefix = "captcha") @Configuration @Data public class CaptchaProperties { private Integer width; private Integer height; private Session session; @Data public static class Session{//直接使用内部类的方式 private String key; private String date; } }此时的 Controller 代码👇
package com.bit.captcha.controller; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.ShearCaptcha; import com.bit.captcha.model.CaptchaProperties; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.Date; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-11-01 * Time: 16:58 */ @RequestMapping("/captcha") @RestController public class CaptchaController { @Autowired//把配置文件的内容注入进来 private CaptchaProperties captchaProperties; //定义过期时间 private final static Long VALID_TIME=30*60*1000L;//30min private String code; //生成验证码 @RequestMapping("/getCaptcha") public void getCaptcha(HttpSession session,HttpServletResponse response){ try { //生成验证码 ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(captchaProperties.getWidth(),captchaProperties.getHeight()); code=captcha.getCode(); //存储到session中 session.setAttribute(captchaProperties.getSession().getKey(),code); //同时也要记录时间,时间过去太久就不校验了,直接返回 false session.setAttribute(captchaProperties.getSession().getDate(),new Date()); //此时我们想把生成的验证码写到浏览器中,需要 response captcha.write(response.getOutputStream()); } catch (IOException e) { throw new RuntimeException(e); } } /** * 验证用户输入的验证码是否正确 * @param captcha:用户输入的验证码 * @param session * @return false:校验失败,true:验证成功 */ @RequestMapping("/check") public boolean check(String captcha,HttpSession session){ //这边就可以从session中去取了 if(!StringUtils.hasLength(captcha)){ return false; } //验证 验证码 String code=(String)session.getAttribute(captchaProperties.getSession().getKey()); Date date=(Date)session.getAttribute(captchaProperties.getSession().getDate()); //验证码正确且没有过期,具体逻辑需要看需求 if(captcha.equalsIgnoreCase(code)&&date!=null&& System.currentTimeMillis()-date.getTime()<VALID_TIME){ return true; } return false; } }测试后端代码👇发现没有问题
验证码项目-后端代码测试
答疑:为什么配置文件上写上👇就报错??
null: value: ~
而写成👇就不报错??
null.value: ~其实上述报错日志写的很清晰了,那是因为 null 是一个关键字,而合在一起写的时候,就认为 null.value 这个整体是 key 了,所以不报错~~
其实上述后端代码运行的时候,咱们通过 Fiddler 抓包就能发现这是一个 .PNG 文件👇
而这正是浏览器自动正确解析的结果,但是并不是每个浏览器都能够正确解析的,所以我们一般情况下为了确保正确解析出来,会在代码中设置 ContentType,我们希望它返回的是一个图片,注意,这这些代码我们一般给写在最上面(你使用 response 之前),而不是最下面,否则不生效的👇
//生成验证码 @RequestMapping("/getCaptcha") public void getCaptcha(HttpSession session,HttpServletResponse response){ response.setContentType("image/jpeg");//设置希望返回的类型 //response.setCharacterEncoding("utf-8");//设置编码格式也在这里 try { //…… }
6.1.6 调整前端页面代码
修改 index.html👇
<img id="verificationCodeImg" src="/admin/captcha" style="cursor: pointer;" title="看不清?换一张" />这一行的 URL 可以是绝对路径,也可以是相对路径,或者是网络路径,而此时我们后端生成的图片就是以网络路径显示的,所以此处改为后端对应的网络路径👇
<img id="verificationCodeImg" src="/captcha/getCaptcha" style="cursor: pointer;" title="看不清?换一张" />启动服务后,重新访问,此时我们的图片就能正常显示了👇
但是这里会有个问题:我要是连续点两次刷新的话,这个图片不会变化,但实际上我们希望它是跟着刷新两次的,这是为什么??
这是因为浏览器会有一些缓存,我们需要去做一些禁止缓存的东西,咱们使用的是 get 请求,这就涉及到 get 请求和 post 请求的区别了,理论上 get 请求是 幂等性的,因为它是从浏览器中获取资源,我们认为获取资源是不会发生变化的,所以浏览器会进行缓存,而如果它认为资源没有发生变化,那么这时候它可能就不请求了~~
关于这里的处理有两种方式:前端处理 和 后端处理
后端处理👇
response.setContentType("image/jpeg");//设置希望返回的类型 //一般情况下,这个处理是前端做,后端不做 response.setHeader("Progma","No-cahce");//加上这一行 // response.setCharacterEncoding("utf-8");//设置编码格式前端处理👇网上随便搜一下:meta progam nocahe,然后加上下面的代码
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" /> <meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Expires" content="0" />补充 ajax 代码,点击提交按钮,发送请求去服务端进行校验
$("#checkCaptcha").click(function () { $.ajax({ type:"post", url:"/captcha/check", data:{ captcha:$("#inputCaptcha").val() }, success:function (result){ if(result){ location.href="success.html"; }else{ alert("验证码错误,请重新输入"); } } });
6.1.7 运行测试
验证码项目运行测试
在前端代码中,这里还有一个知识点给大家提一下👇
$("#verificationCodeImg").click(function(){ $(this).hide().attr('src', '/captcha/getCaptcha?dt=' + new Date().getTime()).fadeIn(); });给这个图片加了一个点击事件,就是我每次点击的时候希望它去请求我们的验证码
后面跟了一个参数 dt,给这个 dt 赋值了一个当前的时间,其目的也是为了防止缓存的,上述加完👇只是说页面不缓存了
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" /> <meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Expires" content="0" />但是这里却是针对每次请求的,每次的 dt 都不一样,浏览器一看第一次 dt 为1,第二次 dt 为2……就不敢缓存了,所以说防止浏览器缓存的方式有很多种~~
答疑:为什么后端代码中的 Session 内部类要加上 static??
那我们把 static 去掉试试看👇
@Data public class Session{//直接使用内部类的方式 private String key; private String date; }启动服务,并刷新页面👇
我们发现这时候它不认识 getKey() 了,找不到了~~
我们如果把这个 Session 类挪出来放在外面的话,不加 static 👇
@Data public class Session { private String key; private String date; }我们发现它是可以运行的,页面也没有问题👇
这就说明写在内部类的话 static 是不能省略的~~
为什么??因为在这里👇
@Configuration 表示我们把这个对象交给 Spring 进行管理了,那 Spring 就会帮我们 new 这个对象了,但是如果是普通内部类的话,Spring 是 new 不了的,赋值时是拿不了的,但是加上 static 之后那就跟 Spring 没关系了,只是写在这里一样而已,这个内部类跟在外面就一样了
这也就说明我们的内部类是不能交给 Spring 进行管理的,但是静态内部类可以,这也是我们加上 static 的原因
简单点说就是,普通内部类不能 IoC,但是静态内部类可以~~
小结
配置项可以是第三方的,也可以是自定义的
配置项分为两种格式:properties 和 yml
properties:单词和单词之间使用点来分割,key 和 value 之间使用等号分割,类型不用管,直接去写,后端使用不同类型去接收
yml:格式是 key: value,这个空格不可省略,单词和单词之间使用冒号+换行去分割,并且 key 不能重复,相同 key 必须放在一起,且各个级别要对齐
yml 可以和 properties 共存,但一个项目中建议使用一种配置类型文件.
7.总结(课件内容)
1. properties 是以 key=value 的形式配置的键值类型的配置文件, yml 使用的是树形配置方式.
2. 读取配置文件内容, 使用 @Value 注解, 注解内使用" ${} "的格式读取.
3. yml 层级之间使用换行缩进的方式配置, key 和 value 之间使用": "(英文冒号)加空格的方式设置, 并且空格不可省略.
4. properties 为早期并且默认的配置文件格式, 其配置存在一定的冗余数据, 使用 yml 可以很好的解决数据冗余的问题, 但不适合复杂配置.
5. yml 可以和 properties 共存,但一个项目中建议使用一种配置类型文件.
8.代码分享
CaptchaController.java
package com.bit.captcha.controller; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.ShearCaptcha; import com.bit.captcha.model.CaptchaProperties; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.Date; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-11-01 * Time: 16:58 */ @RequestMapping("/captcha") @RestController public class CaptchaController { @Autowired//把配置文件的内容注入进来 private CaptchaProperties captchaProperties; //定义过期时间 private final static Long VALID_TIME=30*60*1000L;//30min private String code; //生成验证码 @RequestMapping("/getCaptcha") public void getCaptcha(HttpSession session,HttpServletResponse response){ response.setContentType("image/jpeg");//设置希望返回的类型 //一般情况下,这个处理是前端做,后端不做 response.setHeader("Progma","No-cahce"); // response.setCharacterEncoding("utf-8");//设置编码格式 try { //生成验证码 ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(captchaProperties.getWidth(),captchaProperties.getHeight()); code=captcha.getCode(); //存储到session中 session.setAttribute(captchaProperties.getSession().getKey(),code); //同时也要记录时间,时间过去太久就不校验了,直接返回 false session.setAttribute(captchaProperties.getSession().getDate(),new Date()); //此时我们想把生成的验证码写到浏览器中,需要 response captcha.write(response.getOutputStream()); } catch (IOException e) { throw new RuntimeException(e); } } /** * 验证用户输入的验证码是否正确 * @param captcha:用户输入的验证码 * @param session * @return false:校验失败,true:验证成功 */ @RequestMapping("/check") public boolean check(String captcha,HttpSession session){ //这边就可以从session中去取了 if(!StringUtils.hasLength(captcha)){ return false; } //验证 验证码 String code=(String)session.getAttribute(captchaProperties.getSession().getKey()); Date date=(Date)session.getAttribute(captchaProperties.getSession().getDate()); //验证码正确且没有过期,具体逻辑需要看需求 if(captcha.equalsIgnoreCase(code)&&date!=null&& System.currentTimeMillis()-date.getTime()<VALID_TIME){ return true; } return false; } }CaptchaProperties.java
package com.bit.captcha.model; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-11-01 * Time: 17:29 */ @ConfigurationProperties(prefix = "captcha") @Configuration @Data public class CaptchaProperties { private Integer width; private Integer height; private Session session; @Data public static class Session{//直接使用内部类的方式 private String key; private String date; } }application.yml
#spring.application.name=spring-captcha-demo #将上面 properties 文件中的默认内容自行转成下面的 spring: application: name: spring-captcha-demo captcha: width: 150 height: 50 session: key: CAPTCHA_SESSION_KEY date: CAPTCHA_SESSION_DATE #null.value: ~ #null: # value: ~index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" /> <meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Expires" content="0" /> <title>验证码</title> <style> #inputCaptcha { height: 30px; vertical-align: middle; } #verificationCodeImg{ vertical-align: middle; } #checkCaptcha{ height: 40px; width: 100px; } </style> </head> <body> <h1>输入验证码</h1> <div id="confirm"> <input type="text" name="inputCaptcha" id="inputCaptcha"> <img id="verificationCodeImg" src="/captcha/getCaptcha" style="cursor: pointer;" title="看不清?换一张" /> <input type="button" value="提交" id="checkCaptcha"> </div> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script> <script> $("#verificationCodeImg").click(function(){ $(this).hide().attr('src', '/captcha/getCaptcha?dt=' + new Date().getTime()).fadeIn(); }); $("#checkCaptcha").click(function () { $.ajax({ type:"post", url:"/captcha/check", data:{ captcha:$("#inputCaptcha").val() }, success:function (result){ if(result){ location.href="success.html"; }else{ alert("验证码错误,请重新输入"); } } }); }); </script> </body> </html>success.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>验证成功页</title> </head> <body> <h1>验证成功</h1> </body> </html>