SpringBoot项目实现登录验证码验证

发布于:2025-07-06 ⋅ 阅读:(15) ⋅ 点赞:(0)

SpringBoot项目实现登录验证码验证

验证码是防止暴力破解和机器人攻击的常见手段,下面我会详细介绍如何在SpringBoot项目中实现登录时的验证码验证功能。

一、实现思路

  1. 生成验证码图片
  2. 将验证码文本存入Session或Redis
  3. 前端显示验证码图片
  4. 用户登录时校验验证码
  5. 验证通过后再进行账号密码验证

二、具体实现步骤

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库:

  1. 配置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;
    }
}
  1. 修改控制器:
@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;
    }
}

四、常见问题

  1. 验证码不显示

    • 检查前端是否正确处理了base64图片数据
    • 检查后端是否生成了正确的图片字节流
  2. 验证码总是错误

    • 确保前后端验证码比较时大小写一致(equalsIgnoreCase)
    • 检查Session是否正常工作
    • 分布式环境下需要使用Redis共享验证码
  3. 验证码太简单

    • 增加验证码长度(6-8位)
    • 添加干扰线、干扰点
    • 使用算术验证码(如"1+2=?")
  4. 安全问题

    • 不要在前端返回验证码文本(测试阶段除外)
    • 设置合理的验证码过期时间(3-5分钟)
    • 限制验证码尝试次数(防止暴力破解)

五、总结

实现登录验证码验证主要分为以下几个步骤:

  1. 生成随机验证码文本
  2. 根据文本生成验证码图片
  3. 将验证码文本存储在服务端(Session/Redis)
  4. 前端获取并显示验证码图片
  5. 用户提交时校验验证码
  6. 验证通过后再进行账号密码验证

根据项目需求,你可以选择简单的实现方式,也可以结合Spring Security等框架进行更安全的集成。对于高安全性要求的系统,还可以考虑使用短信验证码、邮箱验证码或更复杂的图形验证码方案。


网站公告

今日签到

点亮在社区的每一天
去签到