SpringBoot项目实现登录验证码验证
验证码是防止暴力破解和机器人攻击的常见手段,下面我会详细介绍如何在SpringBoot项目中实现登录时的验证码验证功能。
一、实现思路
- 生成验证码图片
- 将验证码文本存入Session或Redis
- 前端显示验证码图片
- 用户登录时校验验证码
- 验证通过后再进行账号密码验证
二、具体实现步骤
1. 添加依赖(如果需要图形验证码)
<!-- 验证码生成库(可选) -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2. 创建验证码工具类
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Random;
public class CaptchaUtil {
// 验证码字符集
private static final char[] chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".toCharArray();
// 生成随机验证码
public static String generateCode(int length) {
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(chars[random.nextInt(chars.length)]);
}
return sb.toString();
}
// 生成验证码图片
public static byte[] generateImage(String code) 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);
// 绘制干扰线
Random random = new Random();
for (int i = 0; i < 5; i++) {
g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
g.drawLine(random.nextInt(width), random.nextInt(height),
random.nextInt(width), random.nextInt(height));
}
// 绘制验证码
g.setFont(new Font("Arial", Font.BOLD, 30));
for (int i = 0; i < code.length(); i++) {
g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
g.drawString(String.valueOf(code.charAt(i)), 20 * i + 10, 30);
}
g.dispose();
// 输出图片字节流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", baos);
return baos.toByteArray();
}
}
3. 创建验证码控制器
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
@RestController
public class CaptchaController {
@GetMapping("/captcha")
public Map<String, Object> captcha(HttpServletRequest request) throws IOException {
// 生成4位验证码
String code = CaptchaUtil.generateCode(4);
// 存入session
HttpSession session = request.getSession();
session.setAttribute("captcha", code);
// 生成图片
byte[] imageBytes = CaptchaUtil.generateImage(code);
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
// 返回结果
Map<String, Object> result = new HashMap<>();
result.put("code", code); // 测试时可返回,正式环境不要返回
result.put("image", "data:image/jpeg;base64," + base64Image);
return result;
}
}
4. 修改登录逻辑
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@RestController
public class LoginController {
@PostMapping("/login")
public String login(@RequestBody Map<String, String> params, HttpServletRequest request) {
// 获取用户输入的验证码
String inputCaptcha = params.get("captcha");
if (inputCaptcha == null || inputCaptcha.isEmpty()) {
return "验证码不能为空";
}
// 获取session中的验证码
HttpSession session = request.getSession();
String sessionCaptcha = (String) session.getAttribute("captcha");
// 验证码校验
if (sessionCaptcha == null || !sessionCaptcha.equalsIgnoreCase(inputCaptcha)) {
return "验证码错误";
}
// 验证码使用后清除
session.removeAttribute("captcha");
// 继续验证用户名密码
String username = params.get("username");
String password = params.get("password");
// 这里添加你的用户认证逻辑
// ...
return "登录成功";
}
}
5. 前端实现
<!DOCTYPE html>
<html>
<head>
<title>登录</title>
</head>
<body>
<form id="loginForm">
<div>
<label>用户名:</label>
<input type="text" name="username" required>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" required>
</div>
<div>
<label>验证码:</label>
<input type="text" name="captcha" required>
<img id="captchaImage" src="" alt="验证码" onclick="refreshCaptcha()">
</div>
<button type="button" onclick="login()">登录</button>
</form>
<script>
// 页面加载时获取验证码
window.onload = refreshCaptcha;
// 刷新验证码
function refreshCaptcha() {
fetch('/captcha')
.then(response => response.json())
.then(data => {
document.getElementById('captchaImage').src = data.image;
});
}
// 登录
function login() {
const formData = new FormData(document.getElementById('loginForm'));
const params = {};
formData.forEach((value, key) => params[key] = value);
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
})
.then(response => response.text())
.then(message => {
alert(message);
if (message === '登录成功') {
// 登录成功跳转
window.location.href = '/home';
} else {
// 登录失败刷新验证码
refreshCaptcha();
}
});
}
</script>
</body>
</html>
三、进阶优化
1. 使用Redis存储验证码(分布式环境)
@RestController
public class CaptchaController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping("/captcha")
public Map<String, Object> captcha(HttpServletRequest request) throws IOException {
String code = CaptchaUtil.generateCode(4);
String sessionId = request.getSession().getId();
// 存入Redis,5分钟过期
redisTemplate.opsForValue().set("captcha:" + sessionId, code, 5, TimeUnit.MINUTES);
byte[] imageBytes = CaptchaUtil.generateImage(code);
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
Map<String, Object> result = new HashMap<>();
result.put("image", "data:image/jpeg;base64," + base64Image);
return result;
}
}
2. 使用Spring Security集成验证码
public class CaptchaFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 只拦截登录请求
if ("/login".equals(request.getRequestURI()) && "POST".equals(request.getMethod())) {
String inputCaptcha = request.getParameter("captcha");
HttpSession session = request.getSession();
String sessionCaptcha = (String) session.getAttribute("captcha");
if (inputCaptcha == null || sessionCaptcha == null ||
!sessionCaptcha.equalsIgnoreCase(inputCaptcha)) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"验证码错误\"}");
return;
}
// 验证通过后清除验证码
session.removeAttribute("captcha");
}
filterChain.doFilter(request, response);
}
}
然后在Security配置中添加:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new CaptchaFilter(), UsernamePasswordAuthenticationFilter.class)
// 其他配置...
}
}
3. 使用Google Kaptcha库
如果你不想自己实现图形验证码,可以使用Kaptcha库:
- 配置Kaptcha:
@Configuration
public class KaptchaConfig {
@Bean
public Producer kaptchaProducer() {
Properties properties = new Properties();
properties.setProperty("kaptcha.image.width", "120");
properties.setProperty("kaptcha.image.height", "40");
properties.setProperty("kaptcha.textproducer.font.size", "30");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.char.string", "ABCDEFGHJKLMNPQRSTUVWXYZ23456789");
DefaultKaptcha kaptcha = new DefaultKaptcha();
Config config = new Config(properties);
kaptcha.setConfig(config);
return kaptcha;
}
}
- 修改控制器:
@RestController
public class CaptchaController {
@Autowired
private Producer kaptchaProducer;
@GetMapping("/captcha")
public Map<String, Object> captcha(HttpServletRequest request) throws IOException {
String code = kaptchaProducer.createText();
request.getSession().setAttribute("captcha", code);
BufferedImage image = kaptchaProducer.createImage(code);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", baos);
String base64Image = Base64.getEncoder().encodeToString(baos.toByteArray());
Map<String, Object> result = new HashMap<>();
result.put("image", "data:image/jpeg;base64," + base64Image);
return result;
}
}
四、常见问题
验证码不显示
- 检查前端是否正确处理了base64图片数据
- 检查后端是否生成了正确的图片字节流
验证码总是错误
- 确保前后端验证码比较时大小写一致(equalsIgnoreCase)
- 检查Session是否正常工作
- 分布式环境下需要使用Redis共享验证码
验证码太简单
- 增加验证码长度(6-8位)
- 添加干扰线、干扰点
- 使用算术验证码(如"1+2=?")
安全问题
- 不要在前端返回验证码文本(测试阶段除外)
- 设置合理的验证码过期时间(3-5分钟)
- 限制验证码尝试次数(防止暴力破解)
五、总结
实现登录验证码验证主要分为以下几个步骤:
- 生成随机验证码文本
- 根据文本生成验证码图片
- 将验证码文本存储在服务端(Session/Redis)
- 前端获取并显示验证码图片
- 用户提交时校验验证码
- 验证通过后再进行账号密码验证
根据项目需求,你可以选择简单的实现方式,也可以结合Spring Security等框架进行更安全的集成。对于高安全性要求的系统,还可以考虑使用短信验证码、邮箱验证码或更复杂的图形验证码方案。