Spring Boot图片验证码功能实现详解 - 从零开始到完美运行

Spring Boot图片验证码功能实现详解 - 从零开始到完美运行

📖 前言

大家好!今天我要和大家分享一个非常实用的功能:Spring Boot图片验证码。这个功能可以防止恶意攻击,比如暴力破解、刷票等。我们实现的是一个带有加减法运算的图片验证码,用户需要正确计算结果才能通过验证。

适合人群:Java初学者、Spring Boot新手、想要学习验证码实现的朋友

技术栈:Spring Boot 3.5.5 + Java 17 + Thymeleaf + Maven


🎯 功能预览

最终实现的效果:

  • 生成随机数学表达式(如:5 + 3 = ?)
  • 将表达式绘制成图片
  • 用户输入计算结果
  • 验证答案是否正确
  • 支持刷新验证码

🛠️ 环境准备

1. 创建Spring Boot项目

首先,我们需要创建一个Spring Boot项目。我使用的是Spring Initializr创建的项目,包含以下依赖:

  • Spring Web
  • Thymeleaf
  • Spring Data Redis
  • Spring Session Data Redis

2. 项目结构

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           └── demo/
│   │               ├── Demo5Application.java
│   │               ├── config/
│   │               │   └── CorsConfig.java
│   │               ├── controller/
│   │               │   └── CaptchaController.java
│   │               ├── service/
│   │               │   ├── CaptchaService.java
│   │               │   └── MemoryCaptchaService.java
│   │               └── util/
│   │                   └── CaptchaUtil.java
│   └── resources/
│       ├── application.properties
│       └── templates/
│           └── index.html
└── test/└── java/└── com/└── example/└── demo/└── CaptchaUtilTest.java

第一步:配置Maven依赖

首先,我们需要在pom.xml中添加必要的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.5.5</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>demo5</artifactId><version>0.0.1-SNAPSHOT</version><name>demo5</name><description>demo5</description><properties><java.version>17</java.version></properties><dependencies><!-- Spring Boot Web Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Thymeleaf模板引擎 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- Redis支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Spring Session Redis --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><!-- 测试依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

解释

  • spring-boot-starter-web:提供Web开发基础功能
  • spring-boot-starter-thymeleaf:模板引擎,用于渲染HTML页面
  • spring-boot-starter-data-redis:Redis数据库支持
  • spring-session-data-redis:将Session存储到Redis中

第二步:创建验证码工具类

这是整个功能的核心!我们创建一个工具类来生成数学表达式和绘制图片:

package com.example.demo.util;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Random;/*** 验证码工具类* 用于生成数学表达式验证码图片*/
public class CaptchaUtil {/*** 生成随机数学表达式和答案* @return MathExpression 包含表达式和答案的对象*/public static MathExpression generateMathExpression() {Random random = new Random();int a = random.nextInt(10) + 1; // 1-10int b = random.nextInt(10) + 1; // 1-10// 随机决定是加法还是减法String operator;int result;if (random.nextBoolean()) {operator = "+";result = a + b;} else {operator = "-";// 确保结果不为负数if (a < b) {int temp = a;a = b;b = temp;}result = a - b;}String expression = a + " " + operator + " " + b + " = ?";return new MathExpression(expression, result);}/*** 生成验证码图片* @param expression 数学表达式* @return Base64编码的图片字符串* @throws IOException IO异常*/public static String generateCaptchaImage(String expression) throws IOException {int width = 120;int height = 40;// 创建图片对象BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics2D g = image.createGraphics();// 设置背景色为白色g.setColor(Color.WHITE);g.fillRect(0, 0, width, height);// 设置字体g.setFont(new Font("Arial", Font.BOLD, 16));// 绘制干扰线(让验证码更难被机器识别)Random random = new Random();for (int i = 0; i < 5; i++) {int x1 = random.nextInt(width);int y1 = random.nextInt(height);int x2 = random.nextInt(width);int y2 = random.nextInt(height);g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));g.drawLine(x1, y1, x2, y2);}// 绘制表达式文字g.setColor(Color.BLACK);g.drawString(expression, 10, 25);g.dispose();// 将图片转换为Base64字符串ByteArrayOutputStream baos = new ByteArrayOutputStream();ImageIO.write(image, "png", baos);byte[] bytes = baos.toByteArray();return "data:image/png;base64," + Base64.getEncoder().encodeToString(bytes);}/*** 内部类用于存储表达式和结果*/public static class MathExpression {private String expression;private int result;public MathExpression(String expression, int result) {this.expression = expression;this.result = result;}public String getExpression() {return expression;}public int getResult() {return result;}}
}

代码解释

  1. generateMathExpression()方法

    • 生成两个1-10的随机数
    • 随机选择加法或减法
    • 确保减法结果不为负数
    • 返回表达式字符串和正确答案
  2. generateCaptchaImage()方法

    • 创建120x40像素的图片
    • 设置白色背景
    • 绘制5条随机颜色的干扰线
    • 在图片上绘制数学表达式
    • 将图片转换为Base64字符串返回
  3. MathExpression内部类

    • 封装表达式和答案
    • 提供getter方法

第三步:创建验证码服务类

我们需要两个服务类:一个基于Session,一个基于内存存储。

3.1 基于Session的验证码服务

package com.example.demo.service;import com.example.demo.util.CaptchaUtil;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import jakarta.servlet.http.HttpSession;
import java.io.IOException;/*** 验证码服务类* 处理验证码的生成和验证逻辑*/
@Service
public class CaptchaService {/*** 生成验证码并存入session* @return Base64编码的验证码图片* @throws IOException IO异常*/public String generateCaptcha() throws IOException {CaptchaUtil.MathExpression mathExpression = CaptchaUtil.generateMathExpression();String imageBase64 = CaptchaUtil.generateCaptchaImage(mathExpression.getExpression());// 获取当前session并存储答案ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();HttpSession session = attr.getRequest().getSession();session.setAttribute("captchaAnswer", mathExpression.getResult());session.setMaxInactiveInterval(300); // 5分钟有效期return imageBase64;}/*** 验证用户输入的答案* @param userAnswer 用户输入的答案* @return 验证是否成功*/public boolean validateCaptcha(String userAnswer) {try {int answer = Integer.parseInt(userAnswer);// 获取当前session中的答案ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();HttpSession session = attr.getRequest().getSession();Integer correctAnswer = (Integer) session.getAttribute("captchaAnswer");// 添加调试信息System.out.println("用户输入答案: " + answer);System.out.println("正确答案: " + correctAnswer);System.out.println("Session ID: " + session.getId());if (correctAnswer != null && answer == correctAnswer) {// 验证成功后移除答案session.removeAttribute("captchaAnswer");System.out.println("验证成功");return true;}System.out.println("验证失败");return false;} catch (NumberFormatException e) {System.out.println("数字格式错误: " + userAnswer);return false;}}
}

3.2 基于内存的验证码服务(解决跨域问题)

package com.example.demo.service;import com.example.demo.util.CaptchaUtil;
import org.springframework.stereotype.Service;import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;/*** 基于内存的验证码服务* 解决跨域Session问题*/
@Service
public class MemoryCaptchaService {// 使用ConcurrentHashMap存储验证码,key为验证码ID,value为答案private final Map<String, Integer> captchaStorage = new ConcurrentHashMap<>();/*** 生成验证码并返回验证码ID和图片* @return CaptchaResponse 包含验证码ID和Base64图片* @throws IOException IO异常*/public CaptchaResponse generateCaptcha() throws IOException {CaptchaUtil.MathExpression mathExpression = CaptchaUtil.generateMathExpression();String imageBase64 = CaptchaUtil.generateCaptchaImage(mathExpression.getExpression());// 生成唯一验证码IDString captchaId = UUID.randomUUID().toString();// 存储答案到内存中captchaStorage.put(captchaId, mathExpression.getResult());System.out.println("生成验证码ID: " + captchaId + ", 答案: " + mathExpression.getResult());return new CaptchaResponse(captchaId, imageBase64);}/*** 验证用户输入的答案* @param captchaId 验证码ID* @param userAnswer 用户输入的答案* @return 验证是否成功*/public boolean validateCaptcha(String captchaId, String userAnswer) {try {int answer = Integer.parseInt(userAnswer);Integer correctAnswer = captchaStorage.get(captchaId);System.out.println("验证码ID: " + captchaId);System.out.println("用户输入答案: " + answer);System.out.println("正确答案: " + correctAnswer);if (correctAnswer != null && answer == correctAnswer) {// 验证成功后移除验证码captchaStorage.remove(captchaId);System.out.println("验证成功");return true;}System.out.println("验证失败");return false;} catch (NumberFormatException e) {System.out.println("数字格式错误: " + userAnswer);return false;}}/*** 验证码响应类*/public static class CaptchaResponse {private String captchaId;private String imageBase64;public CaptchaResponse(String captchaId, String imageBase64) {this.captchaId = captchaId;this.imageBase64 = imageBase64;}public String getCaptchaId() {return captchaId;}public String getImageBase64() {return imageBase64;}}
}

两种服务类的区别

  1. Session服务:将答案存储在HttpSession中,适合同域访问
  2. 内存服务:将答案存储在内存Map中,通过唯一ID关联,解决跨域问题

第四步:创建控制器

控制器负责处理HTTP请求,连接前端和后端:

package com.example.demo.controller;import com.example.demo.service.CaptchaService;
import com.example.demo.service.MemoryCaptchaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/*** 验证码控制器* 处理验证码相关的HTTP请求*/
@Controller
public class CaptchaController {@Autowiredprivate CaptchaService captchaService;@Autowiredprivate MemoryCaptchaService memoryCaptchaService;/*** 显示验证页面* @param model 模型对象* @return 页面名称*/@GetMapping("/")public String index(Model model) {return "index";}/*** 获取验证码图片(基于Session)* @return Base64编码的验证码图片*/@GetMapping("/captcha")@ResponseBodypublic ResponseEntity<String> getCaptcha() {try {String imageBase64 = captchaService.generateCaptcha();return ResponseEntity.ok(imageBase64);} catch (IOException e) {return ResponseEntity.status(500).body("生成验证码失败");}}/*** 验证答案(基于Session)* @param answer 用户输入的答案* @return 验证结果*/@PostMapping("/validate")@ResponseBodypublic ResponseEntity<String> validateCaptcha(@RequestParam String answer) {System.out.println("收到验证请求,答案: " + answer);boolean isValid = captchaService.validateCaptcha(answer);if (isValid) {return ResponseEntity.ok("验证成功");} else {return ResponseEntity.badRequest().body("验证失败");}}/*** 获取基于内存的验证码(解决跨域Session问题)* @return 包含验证码ID和图片的响应*/@GetMapping("/memory-captcha")@ResponseBodypublic ResponseEntity<Map<String, String>> getMemoryCaptcha() {try {MemoryCaptchaService.CaptchaResponse response = memoryCaptchaService.generateCaptcha();Map<String, String> result = new HashMap<>();result.put("captchaId", response.getCaptchaId());result.put("imageData", response.getImageBase64());return ResponseEntity.ok(result);} catch (IOException e) {return ResponseEntity.status(500).build();}}/*** 验证基于内存的验证码* @param captchaId 验证码ID* @param answer 用户输入的答案* @return 验证结果*/@PostMapping("/memory-validate")@ResponseBodypublic ResponseEntity<String> validateMemoryCaptcha(@RequestParam String captchaId, @RequestParam String answer) {System.out.println("收到内存验证码验证请求,ID: " + captchaId + ", 答案: " + answer);boolean isValid = memoryCaptchaService.validateCaptcha(captchaId, answer);if (isValid) {return ResponseEntity.ok("验证成功");} else {return ResponseEntity.badRequest().body("验证失败");}}
}

控制器解释

  • @GetMapping("/"):显示主页面
  • @GetMapping("/captcha"):获取Session验证码
  • @PostMapping("/validate"):验证Session验证码
  • @GetMapping("/memory-captcha"):获取内存验证码
  • @PostMapping("/memory-validate"):验证内存验证码

第五步:创建前端页面

创建一个美观的HTML页面:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>验证码演示</title><style>body {font-family: Arial, sans-serif;margin: 50px;background-color: #f5f5f5;}.container {max-width: 400px;margin: 0 auto;padding: 20px;background-color: white;border-radius: 5px;box-shadow: 0 0 10px rgba(0,0,0,0.1);}.form-group {margin-bottom: 15px;}label {display: block;margin-bottom: 5px;}input[type="text"] {width: 100%;padding: 8px;border: 1px solid #ddd;border-radius: 4px;box-sizing: border-box;}button {padding: 10px 15px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #45a049;}#captchaImage {margin-bottom: 10px;border: 1px solid #ddd;}#refreshCaptcha {margin-left: 10px;background-color: #2196F3;}#refreshCaptcha:hover {background-color: #1976D2;}.message {margin-top: 15px;padding: 10px;border-radius: 4px;display: none;}.success {background-color: #dff0d8;color: #3c763d;border: 1px solid #d6e9c6;}.error {background-color: #f2dede;color: #a94442;border: 1px solid #ebccd1;}.captcha-container {display: flex;align-items: center;margin-bottom: 10px;}</style>
</head>
<body><div class="container"><h2>验证码验证</h2><div class="form-group"><label>验证码:</label><div class="captcha-container"><img id="captchaImage" src="" alt="验证码"><button id="refreshCaptcha">刷新</button></div></div><div class="form-group"><label for="answer">请输入计算结果:</label><input type="text" id="answer" placeholder="请输入计算结果"></div><!-- 隐藏的验证码ID字段 --><input type="hidden" id="captchaId" value=""><button id="submitBtn">提交验证</button><div id="message" class="message"></div></div><script>document.addEventListener('DOMContentLoaded', function() {const captchaImage = document.getElementById('captchaImage');const refreshBtn = document.getElementById('refreshCaptcha');const answerInput = document.getElementById('answer');const submitBtn = document.getElementById('submitBtn');const messageDiv = document.getElementById('message');const captchaIdInput = document.getElementById('captchaId');// 加载验证码function loadCaptcha() {fetch('http://localhost:8080/memory-captcha').then(response => {if (!response.ok) {throw new Error('网络响应不正常');}return response.json();}).then(data => {captchaImage.src = data.imageData;captchaIdInput.value = data.captchaId;console.log('验证码ID:', data.captchaId);}).catch(error => {console.error('加载验证码失败:', error);showMessage('加载验证码失败,请重试', 'error');});}// 初始化加载验证码loadCaptcha();// 刷新验证码refreshBtn.addEventListener('click', function() {loadCaptcha();answerInput.value = '';messageDiv.style.display = 'none';});// 提交验证submitBtn.addEventListener('click', function() {const answer = answerInput.value.trim();const captchaId = captchaIdInput.value;if (!answer) {showMessage('请输入计算结果', 'error');return;}if (!captchaId) {showMessage('验证码已过期,请刷新', 'error');loadCaptcha();return;}// 发送验证请求const formData = new FormData();formData.append('captchaId', captchaId);formData.append('answer', answer);fetch('http://localhost:8080/memory-validate', {method: 'POST',body: formData}).then(response => {if (response.ok) {return response.text();} else {throw new Error('验证失败');}}).then(result => {showMessage('验证成功!', 'success');}).catch(error => {showMessage('验证失败,请重试', 'error');loadCaptcha(); // 刷新验证码answerInput.value = '';});});// 显示消息function showMessage(message, type) {messageDiv.textContent = message;messageDiv.className = 'message ' + type;messageDiv.style.display = 'block';}});</script>
</body>
</html>

前端功能解释

  1. 页面加载时:自动获取验证码图片
  2. 刷新按钮:重新获取新的验证码
  3. 提交验证:发送用户输入的答案到后端验证
  4. 消息提示:显示验证成功或失败的消息

⚙️ 第六步:配置CORS和应用程序

6.1 CORS配置(解决跨域问题)

package com.example.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** CORS配置类* 解决跨域请求问题*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*").allowCredentials(true).maxAge(3600);}@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.addAllowedOriginPattern("*");configuration.addAllowedMethod("*");configuration.addAllowedHeader("*");configuration.setAllowCredentials(true);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);return source;}
}

6.2 应用程序配置

# application.properties
spring.application.name=demo5# 服务器配置
server.port=8080# Session配置
server.servlet.session.timeout=300s# Redis配置(可选,如果不使用Redis可以注释掉)
# spring.redis.host=localhost
# spring.redis.port=6379
# spring.redis.password=
# spring.redis.database=0# 使用Redis存储Session(可选)
# spring.session.store-type=redis# 日志配置
logging.level.com.example.demo=DEBUG

第七步:创建测试类

为了确保我们的代码正常工作,我们创建一个测试类:

package com.example.demo;import com.example.demo.util.CaptchaUtil;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;/*** 验证码工具类测试*/
public class CaptchaUtilTest {@Testpublic void testGenerateMathExpression() {// 测试生成数学表达式CaptchaUtil.MathExpression expression = CaptchaUtil.generateMathExpression();assertNotNull(expression);assertNotNull(expression.getExpression());assertTrue(expression.getExpression().contains("="));assertTrue(expression.getExpression().contains("?"));assertTrue(expression.getResult() >= 0); // 结果应该非负}@Testpublic void testGenerateCaptchaImage() throws Exception {// 测试生成验证码图片String expression = "5 + 3 = ?";String imageBase64 = CaptchaUtil.generateCaptchaImage(expression);assertNotNull(imageBase64);assertTrue(imageBase64.startsWith("data:image/png;base64,"));assertTrue(imageBase64.length() > 100); // Base64字符串应该有一定长度}@Testpublic void testMathExpressionClass() {// 测试MathExpression内部类String expression = "2 + 3 = ?";int result = 5;CaptchaUtil.MathExpression mathExpression = new CaptchaUtil.MathExpression(expression, result);assertEquals(expression, mathExpression.getExpression());assertEquals(result, mathExpression.getResult());}
}

第八步:运行和测试

8.1 启动应用

  1. 在IDE中运行Demo5Application.java
  2. 或者使用命令行:mvn spring-boot:run
  3. 应用启动后访问:http://localhost:8080

8.2 测试功能

  1. 页面加载:自动显示验证码图片
  2. 计算答案:根据图片中的数学表达式计算答案
  3. 输入答案:在输入框中输入计算结果
  4. 提交验证:点击"提交验证"按钮
  5. 刷新验证码:点击"刷新"按钮获取新的验证码

遇到的问题和解决方案

问题1:HttpSession无法解析

错误信息

无法解析符号 'HttpSession'

原因:Spring Boot 3.x使用Jakarta EE,javax.servlet包被替换为jakarta.servlet

解决方案

// 错误的导入
import javax.servlet.http.HttpSession;// 正确的导入
import jakarta.servlet.http.HttpSession;

问题2:CORS跨域问题

错误信息

Access to fetch at 'http://localhost:8080/captcha' from origin 'null' has been blocked by CORS policy

原因:当通过IDE预览页面时,页面运行在不同端口,浏览器阻止跨域请求

解决方案

  1. 创建CORS配置类
  2. 使用内存验证码服务替代Session验证码
  3. 前端使用绝对URL请求

问题3:Session无法跨域共享

错误信息

验证总是失败,Session中的答案为空

原因:跨域请求时Session无法正确共享

解决方案

  1. 创建基于内存的验证码服务
  2. 使用唯一ID关联验证码和答案
  3. 前端传递验证码ID进行验证

问题4:Maven命令无法识别

错误信息

mvn : 无法将"mvn"项识别为 cmdlet、函数、脚本文件或可运行程序的名称

原因:Maven没有安装或没有配置环境变量

解决方案

  1. 安装Maven并配置环境变量
  2. 或者直接使用IDE运行应用
  3. 或者使用项目自带的Maven Wrapper:./mvnw spring-boot:run

📊 功能特点总结

✅ 已实现的功能

  1. 数学表达式验证码:比传统字符验证码更友好
  2. 图片生成:自动绘制验证码图片
  3. 双重存储方案:支持Session和内存两种存储方式
  4. 跨域支持:解决了IDE预览时的跨域问题
  5. 美观界面:现代化的UI设计
  6. 调试信息:控制台输出详细的验证过程
  7. 自动刷新:验证失败后自动刷新验证码

🎯 技术亮点

  1. Base64图片编码:直接在前端显示图片
  2. ConcurrentHashMap:线程安全的内存存储
  3. UUID唯一标识:确保验证码ID的唯一性
  4. CORS配置:完整的跨域解决方案
  5. 异常处理:完善的错误处理机制

🔮 扩展功能建议

1. 添加Redis存储

// 可以扩展为使用Redis存储验证码
@Autowired
private StringRedisTemplate redisTemplate;public void storeCaptcha(String captchaId, int answer) {redisTemplate.opsForValue().set("captcha:" + captchaId, String.valueOf(answer), 5, TimeUnit.MINUTES);
}

2. 增加验证码复杂度

// 可以添加更多运算符
String[] operators = {"+", "-", "*", "/"};
// 可以增加数字范围
int a = random.nextInt(20) + 1; // 1-20

3. 添加验证码过期机制

// 可以添加定时清理过期验证码
@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void cleanExpiredCaptchas() {// 清理逻辑
}

4. 增加验证码样式

// 可以添加更多干扰元素
g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
g.fillOval(random.nextInt(width), random.nextInt(height), 10, 10);

学习总结

通过这个项目,我们学会了:

  1. Spring Boot基础:如何创建Web应用
  2. 图片处理:使用Java Graphics API绘制图片
  3. Base64编码:图片的编码和解码
  4. Session管理:HttpSession的使用
  5. 跨域问题:CORS的配置和解决
  6. 前端交互:JavaScript与后端API的交互
  7. 异常处理:完善的错误处理机制
  8. 测试驱动:编写单元测试验证功能

结语

恭喜你!你已经成功实现了一个完整的Spring Boot图片验证码功能。这个项目涵盖了:

  • ✅ 后端API开发
  • ✅ 图片生成和处理
  • ✅ 前端页面开发
  • ✅ 跨域问题解决
  • ✅ 异常处理
  • ✅ 单元测试

这个验证码功能可以应用到实际的Web项目中,有效防止恶意攻击。希望这个教程对你有帮助!

如果你有任何问题或建议,欢迎在评论区留言讨论!


项目源码:所有代码都已经在文章中完整提供,可以直接复制使用。

运行环境:Java 17 + Spring Boot 3.5.5 + Maven

测试地址:http://localhost:8080

祝学习愉快!🚀

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98537.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98537.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS实现快递APP自动识别地址

​ 大家好&#xff0c;我是潘Sir&#xff0c;持续分享IT技术&#xff0c;帮你少走弯路。《鸿蒙应用开发从入门到项目实战》系列文章持续更新中&#xff0c;欢迎关注&#xff01; 随着鸿蒙&#xff08;HarmonyOS&#xff09;生态发展&#xff0c;越来越多的APP需要进行鸿蒙适…

CUDA编程13 - 测量每个Block的执行时间

一:概述 GPU 程序性能不是靠 CPU 那样的“顺序执行”来衡量的,而是靠线程块(block)和多处理器(SM)利用率。每个 block 在 GPU 的不同多处理器上执行,顺序不确定。传统的 kernel 总体计时(比如 cudaEvent 计时整个 kernel)只能知道总时间,无法分析哪个 block 慢,为什…

敏捷开发-Scrum(下)

Scrum 核心构成&#xff1a;团队、事件与工件的协同价值体系 在 Scrum 框架中&#xff0c;“团队、事件、工件” 并非孤立的模块&#xff0c;而是相互咬合的有机整体&#xff1a;Scrum 团队是价值交付的执行核心&#xff0c;Scrum 事件是节奏把控与反馈调整的机制载体&#xff…

LeetCode 单调栈 739. 每日温度

739. 每日温度给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 示例 1: 输入…

Java-面试八股文-JVM篇

JVM篇 一.在JVM中&#xff0c;什么是程序计数器? 在 JVM&#xff08;Java Virtual Machine&#xff09; 中&#xff0c;程序计数器&#xff08;Program Counter Register&#xff0c;简称 PC 寄存器&#xff09; 是一块较小的内存空间&#xff0c;用于记录 当前线程所执行的字…

微算法科技(NASDAQ: MLGO)采用量子相位估计(QPE)方法,增强量子神经网络训练

随着量子计算技术的迅猛发展&#xff0c;传统计算机在处理复杂问题时所遇到的算力瓶颈日益凸显。量子计算以其独特的并行计算能力和指数级增长的计算潜力&#xff0c;为解决这些问题提供了新的途径。微算法科技&#xff08;NASDAQ: MLGO&#xff09;探索量子技术在各种应用场景…

MySQL 备份的方法和最佳实践

MySQL 是一种流行的开源关系数据库管理系统&#xff0c;用于在线应用程序和数据仓库。它以可靠性、有效性和简单性而闻名。然而&#xff0c;与任何计算机系统一样&#xff0c;由于硬件故障、软件缺陷或其他不可预见的情况&#xff0c;存在数据丢失的可能性。因此&#xff0c;保…

应用层自定义协议、序列化和反序列化

1.自定义协议开发者根据特定应用场景的需要&#xff0c;自行设计和制定的通信规则和数据格式 1.1 核心组成部分一个典型的自定义协议通常包含以下几个关键部分&#xff1a;​帧/报文格式 (Frame/Packet Format)​​&#xff1a;定义了数据是如何打包的。这通常包括&#xff1a…

Excel VBA 中可用的工作表函数

Visual Basic for Applications (VBA) 中可用的工作表函数。可以在 VBA 中通过 Application.WorksheetFunction 对象调用。 下面我将按照字母分组&#xff0c;对每个函数进行简要解释&#xff0c;并给出在 VBA 中使用的示例。A 组Acos: 返回数字的反余弦值。 result Applicati…

OpenWrt + Docker 完整部署方案:CFnat + Cloudflared 一体化集成

AI生成&#xff08;可能是AI幻觉&#xff09; 项目架构概述 基于您现有的网络配置&#xff08;IP: 192.168.1.1&#xff09;&#xff0c;本方案将CFnat服务作为网络优化层整合到现有的Cloudflare隧道架构中&#xff0c;实现完整的网络加速解决方案。 优化后的流量路径 用户访问…

苍穹外卖项目实战(day7-1)-缓存菜品和缓存套餐功能-记录实战教程、问题的解决方法以及完整代码

完整资料下载 通过网盘分享的文件&#xff1a;苍穹外卖 链接: https://pan.baidu.com/s/1JJaFOodXOF_lNJSUiZ6qtw?pwdps2t 提取码: ps2t 目录 1、缓存菜品 &#xff08;1&#xff09;问题说明 &#xff08;2&#xff09;使用redis缓存部分数据 1-2、代码完善 &#xff…

计算机毕业设计 基于Python+Django的医疗数据分析系统

精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、项目介绍二…

使用 chromedp 高效爬取 Bing 搜索结果

在数据采集领域&#xff0c;搜索引擎结果是重要的信息来源。但传统爬虫面对现代浏览器渲染的页面时&#xff0c;常因 JavaScript 动态加载、跳转链接加密等问题束手无策。本文将详细介绍如何使用 Go 语言的chromedp库&#xff0c;模拟真实浏览器行为爬取 Bing 搜索结果&#xf…

遗漏的需求

“编写执行者的目的&#xff0c;仅用别名来表达需要传递的数据”&#xff0c;就如客户信息用名字和地址表示一样&#xff0c;这是一个很好的建议。然而&#xff0c;对程序员来说&#xff0c;这没有提供软件开发所必需的详细信息。程序设计人员和用户界面设计者需要准确地知道地…

《云原生故障诊疗指南:从假活到配置漂移的根治方案》

当云原生架构成为企业数字化转型的标配,系统故障的形态也随之发生了根本性变化。曾经那些“一目了然”的报错信息逐渐消失,取而代之的是“指标正常却服务不可用”“偶发故障无规律可循”等隐性问题。这些故障如同架构中的“暗物质”,看不见却持续影响着系统的稳定性,其排查…

“从零到一:使用GitLab和Jenkins实现自动化CI/CD流水线”

GitLab仓库 简单的来说就是开发人员提交代码的仓库&#xff0c;用于团队开发&#xff0c;GitLab 上托管的仓库通常作为远程仓库使用&#xff0c;开发人员可以将本地的 Git 仓库推送到 GitLab 上&#xff0c;也可以从 GitLab 克隆仓库到本地进行开发。 Jenkins Jenkins 是一个开…

3D开发工具HOOPS助力造船业数字化转型,打造更高效、更智能的船舶设计与协作!

造船业是一个高度复杂且竞争激烈的行业&#xff0c;涵盖船体设计、结构分析、生产制造到运维管理的完整生命周期。面对庞大的CAD数据、多方协作的复杂流程以及数字化转型的迫切需求&#xff0c;传统工具往往显得力不从心。 Tech Soft 3D的HOOPS SDK系列&#xff0c;正以其卓越…

Python调用MCP:无需重构,快速为现有应用注入AI与外部服务能力!

文章目录 📖 介绍 📖 🏡 演示环境 🏡 ✨ MCP核心概念:AI世界的“USB-C” ✨ 🛠️ MCP安装与基础使用 🛠️ 🚀 安装模块 📝 创建第一个MCP服务端 📞 Python中MCP客户端的调用方案 📞 📖 概述 📑 深度解析 🔖 参数详情 🔖 常用方法 🚀 不同传输协…

【链表】3.重排链表(medium)

重排链表&#xff08;medium&#xff09;题⽬描述&#xff1a;解法&#xff1a;算法思路&#xff1a;算法代码&#xff1a;题⽬链接&#xff1a;143. 重排链表 题⽬描述&#xff1a; 给定⼀个单链表 L 的头节点 head &#xff0c;单链表 L 表⽰为&#xff1a; L(0) → L(1) →…

蜜罐平台-Hfish部署

Hfish简介&#xff1a; HFish是一款社区型免费蜜罐&#xff0c;侧重企业安全场景&#xff0c;从内网失陷检测、外网威胁感知、威胁情报生产三个场景出发&#xff0c;为用户提供可独立操作且实用的功能&#xff0c;通过安全、敏捷、可靠的中低交互蜜罐增加用户在失陷感知和威胁…