SpringBoot中3种登录验证码实现方案

SpringBoot中3种登录验证码实现方案前言 登录验证码是一种简单而有效的安全措施 广泛应用于各种在线系统和服务中 它在保护用户账户安全和防止恶意行为方面起着至关重要的作用

大家好,欢迎来到IT知识分享网。

前言

登录验证码是一种简单而有效的安全措施,广泛应用于各种在线系统和服务中。它在保护用户账户安全和防止恶意行为方面起着至关重要的作用。

基于Kaptcha的图形验证码

图形验证码是最传统且应用最广泛的验证码类型,原理是在服务端生成随机字符串并渲染成图片,用户需要识别图片中的字符并输入。图形验证码实现简单,对用户体验影响较小,是中小型应用的理想选择。

实现步骤

添加依赖

<dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency>

配置Kaptcha生成器

@Configuration public class KaptchaConfig { @Bean public Producer kaptchaProducer() { Properties properties = new Properties(); // 图片宽度 properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "150"); // 图片高度 properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "50"); // 字体大小 properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "32"); // 字体颜色 properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); // 文本集合,验证码值从此集合中获取 properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0ABCDEFGHIJKLMNOPQRSTUVWXYZ"); // 验证码长度 properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); // 干扰线颜色 properties.setProperty(Constants.KAPTCHA_NOISE_COLOR, "blue"); // 去除背景渐变 properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); Config config = new Config(properties); return config.getProducerImpl(); } } 

创建验证码存储

@Service public class CaptchaService { @Autowired private RedisTemplate<String, String> redisTemplate; // 验证码有效期(秒) private static final long CAPTCHA_EXPIRATION = 300; // 存储验证码 public void storeCaptcha(String sessionId, String captchaCode) { redisTemplate.opsForValue().set( "CAPTCHA:" + sessionId, captchaCode, CAPTCHA_EXPIRATION, TimeUnit.SECONDS); } // 验证验证码 public boolean validateCaptcha(String sessionId, String userInputCaptcha) { String key = "CAPTCHA:" + sessionId; String storedCaptcha = redisTemplate.opsForValue().get(key); if (storedCaptcha != null && storedCaptcha.equalsIgnoreCase(userInputCaptcha)) { // 验证成功后立即删除,防止重复使用 redisTemplate.delete(key); return true; } return false; } } 

实现验证码生成控制器

@RestController @RequestMapping("/captcha") public class CaptchaController { @Autowired private Producer kaptchaProducer; @Autowired private CaptchaService captchaService; @GetMapping("/image") public void getImageCaptcha(HttpServletResponse response, HttpServletRequest request) throws IOException { // 清除浏览器缓存 response.setDateHeader("Expires", 0); response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); response.addHeader("Cache-Control", "post-check=0, pre-check=0"); response.setHeader("Pragma", "no-cache"); response.setContentType("image/jpeg"); // 创建验证码文本 String capText = kaptchaProducer.createText(); // 获取会话ID String sessionId = request.getSession().getId(); // 存储验证码 captchaService.storeCaptcha(sessionId, capText); // 创建验证码图片 BufferedImage image = kaptchaProducer.createImage(capText); ServletOutputStream out = response.getOutputStream(); // 输出图片 ImageIO.write(image, "jpg", out); out.flush(); out.close(); } }

登录控制器集成验证码校验

@RestController @RequestMapping("/auth") public class AuthController { @Autowired private CaptchaService captchaService; @Autowired private UserService userService; @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest, HttpServletRequest request) { // 获取会话ID String sessionId = request.getSession().getId(); // 验证验证码 if (!captchaService.validateCaptcha(sessionId, loginRequest.getCaptcha())) { return ResponseEntity.badRequest().body(new ApiResponse(false, "验证码错误或已过期")); } // 用户名密码验证 boolean authenticated = userService.authenticate( loginRequest.getUsername(), loginRequest.getPassword()); if (authenticated) { // 生成Token或设置会话 String token = jwtTokenProvider.generateToken(loginRequest.getUsername()); return ResponseEntity.ok(new JwtAuthResponse(token)); } else { return ResponseEntity.badRequest().body(new ApiResponse(false, "用户名或密码错误")); } } } 

优点

  • 实现简单,开发成本低
  • 对服务器资源占用少
  • 用户体验相对友好
  • 无需第三方服务,降低依赖

缺点

  • 安全性较低,容易被OCR识别激活成功教程
  • 对视障用户不友好
  • 在移动设备上体验不佳
  • 随着AI发展,图形验证码的安全性逐渐降低

方案二:基于短信验证码

短信验证码通过向用户手机发送一次性验证码实现身份验证。用户需要输入收到的验证码完成登录过程。这种方式不仅验证了账号密码的正确性,还确认了用户对手机号的控制权,大幅提高了安全性。

实现步骤

添加阿里云SDK依赖

<dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.3</version> </dependency>

配置短信服务参数

@Configuration @ConfigurationProperties(prefix = "aliyun.sms") @Data public class SmsProperties { private String accessKeyId; private String accessKeySecret; private String signName; private String templateCode; private String endpoint = "dysmsapi.aliyuncs.com"; }
# application.properties aliyun.sms.access-key-id=YOUR_ACCESS_KEY_ID aliyun.sms.access-key-secret=YOUR_ACCESS_KEY_SECRET aliyun.sms.sign-name=YOUR_SMS_SIGN_NAME aliyun.sms.template-code=SMS_TEMPLATE_CODE

实现短信服务

@Service @Slf4j public class SmsService { @Autowired private SmsProperties smsProperties; @Autowired private RedisTemplate<String, String> redisTemplate; private static final long SMS_EXPIRATION = 300; // 5分钟过期 private static final String SMS_PREFIX = "SMS_CAPTCHA:"; / * 发送短信验证码 * @param phoneNumber 手机号 * @return 是否发送成功 */ public boolean sendSmsCaptcha(String phoneNumber) { try { // 生成6位随机验证码 String captcha = generateCaptcha(6); // 存储验证码 redisTemplate.opsForValue().set( SMS_PREFIX + phoneNumber, captcha, SMS_EXPIRATION, TimeUnit.SECONDS); // 构建短信客户端 DefaultProfile profile = DefaultProfile.getProfile( "cn-hangzhou", smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret()); IAcsClient client = new DefaultAcsClient(profile); // 构建短信请求 CommonRequest request = new CommonRequest(); request.setSysMethod(MethodType.POST); request.setSysDomain(smsProperties.getEndpoint()); request.setSysVersion("2017-05-25"); request.setSysAction("SendSms"); request.putQueryParameter("RegionId", "cn-hangzhou"); request.putQueryParameter("PhoneNumbers", phoneNumber); request.putQueryParameter("SignName", smsProperties.getSignName()); request.putQueryParameter("TemplateCode", smsProperties.getTemplateCode()); // 设置模板参数,将验证码作为参数传入 Map<String, String> templateParam = Map.of("code", captcha); request.putQueryParameter("TemplateParam", new ObjectMapper().writeValueAsString(templateParam)); // 发送短信 CommonResponse response = client.getCommonResponse(request); log.info("短信发送结果: {}", response.getData()); // 解析响应 JsonNode responseJson = new ObjectMapper().readTree(response.getData()); return "OK".equals(responseJson.get("Code").asText()); } catch (Exception e) { log.error("发送短信验证码失败", e); return false; } } / * 验证短信验证码 * @param phoneNumber 手机号 * @param captcha 用户输入的验证码 * @return 是否验证成功 */ public boolean validateSmsCaptcha(String phoneNumber, String captcha) { String key = SMS_PREFIX + phoneNumber; String storedCaptcha = redisTemplate.opsForValue().get(key); if (storedCaptcha != null && storedCaptcha.equals(captcha)) { // 验证成功后删除验证码,防止重复使用 redisTemplate.delete(key); return true; } return false; } / * 生成指定长度的随机数字验证码 */ private String generateCaptcha(int length) { StringBuilder captcha = new StringBuilder(); Random random = new Random(); for (int i = 0; i < length; i++) { captcha.append(random.nextInt(10)); } return captcha.toString(); } } 

实现短信验证码控制器

@RestController @RequestMapping("/captcha") public class SmsCaptchaController { @Autowired private SmsService smsService; @PostMapping("/sms/send") public ResponseEntity<?> sendSmsCaptcha(@RequestBody PhoneNumberRequest request) { String phoneNumber = request.getPhoneNumber(); // 验证手机号格式 if (!isValidPhoneNumber(phoneNumber)) { return ResponseEntity.badRequest().body(new ApiResponse(false, "手机号格式不正确")); } // 发送短信验证码 boolean sent = smsService.sendSmsCaptcha(phoneNumber); if (sent) { return ResponseEntity.ok(new ApiResponse(true, "验证码已发送,请注意查收")); } else { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ApiResponse(false, "验证码发送失败,请稍后再试")); } } // 验证手机号格式(简化版) private boolean isValidPhoneNumber(String phoneNumber) { return phoneNumber != null && phoneNumber.matches("^1[3-9]\d{9}#34;); } } 

登录控制器集成短信验证码

@RestController @RequestMapping("/auth") public class SmsLoginController { @Autowired private SmsService smsService; @Autowired private UserService userService; @Autowired private JwtTokenProvider jwtTokenProvider; @PostMapping("/sms-login") public ResponseEntity<?> smsLogin(@RequestBody SmsLoginRequest request) { String phoneNumber = request.getPhoneNumber(); String captcha = request.getCaptcha(); // 验证短信验证码 if (!smsService.validateSmsCaptcha(phoneNumber, captcha)) { return ResponseEntity.badRequest().body(new ApiResponse(false, "验证码错误或已过期")); } // 查找或创建用户 User user = userService.findOrCreateByPhone(phoneNumber); if (user != null) { // 生成JWT令牌 String token = jwtTokenProvider.generateToken(user.getUsername()); return ResponseEntity.ok(new JwtAuthResponse(token)); } else { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ApiResponse(false, "登录失败,请稍后再试")); } } } 

优点

  • 安全性高,验证了用户对手机的控制权
  • 可以作为无密码登录的方式,简化用户操作
  • 适合移动应用和需要高安全性的场景
  • 可与其他验证方式结合,实现多因素认证

缺点

  • 依赖第三方短信服务,存在成本
  • 可能受网络和运营商影响,导致延迟
  • 对国际用户不友好
  • 用户频繁登录时,体验不佳

方案三:基于AJ-Captcha的滑动拼图验证码

滑动拼图验证码是一种更现代的验证方式,通过让用户拖动滑块完成拼图来验证人机交互。这种验证码在用户体验和安全性之间取得了很好的平衡,既能有效防止自动化攻击,又不会像传统图形验证码那样影响用户体验。

实现步骤

加AJ-Captcha依赖

<dependency> <groupId>com.anji-plus</groupId> <artifactId>captcha-spring-boot-starter</artifactId> <version>1.4.0</version> </dependency>

配置滑动验证码参数

aj: captcha: cache-type: local expires-in: 300 req-frequency-limit-count: 50 cache-number: 1000 jigsaw: classpath:images/jigsaw pic-click: classpath:images/pic-click 

自定义滑动验证码控制器

 @RestController @RequestMapping("/captcha") public class JigsawCaptchaController { @Autowired private CaptchaService captchaService; @RequestMapping(value = "/get", method = {RequestMethod.GET, RequestMethod.POST}) public ResponseModel get(@RequestParam(value = "type", defaultValue = "slide") String captchaType) { CaptchaVO captchaVO = new CaptchaVO(); captchaVO.setCaptchaType(captchaType); return captchaService.get(captchaVO); } @PostMapping("/check") public ResponseModel check(@RequestBody CaptchaVO captchaVO) { return captchaService.check(captchaVO); } } 

登录控制器集成滑动验证码

 @RestController @RequestMapping("/auth") public class JigsawLoginController { @Autowired private CaptchaService captchaService; @PostMapping("/jigsaw-login") public ResponseEntity<?> jigsawLogin(@RequestBody JigsawLoginRequest request) { // 验证滑动验证码 CaptchaVO captchaVO = new CaptchaVO(); captchaVO.setCaptchaVerification(request.getCaptchaVerification()); ResponseModel response = captchaService.verification(captchaVO); if (!response.isSuccess()) { return ResponseEntity.badRequest().body(new ApiResponse(false, "验证码校验失败")); } // TODO 模拟验证用户名密码 boolean authenticated = request.getPassword().equals("admin"); if (authenticated) { // TODO 模拟生成令牌 String token = IdUtil.simpleUUID(); return ResponseEntity.ok(new JwtAuthResponse(token)); } else { return ResponseEntity.badRequest().body(new ApiResponse(false, "用户名或密码错误")); } } } 
@SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } @Bean public CaptchaService captchaService(){ BlockPuzzleCaptchaServiceImpl clickWordCaptchaService = new BlockPuzzleCaptchaServiceImpl(); Properties properties = new Properties(); properties.setProperty(Const.CAPTCHA_FONT_TYPE,"WenQuanZhengHei.ttf"); clickWordCaptchaService.init(properties); return clickWordCaptchaService; } } 

优点

  • 用户体验良好,交互更加自然
  • 安全性较高,难以被自动化工具激活成功教程
  • 支持移动端和桌面端
  • 可定制化程度高,支持品牌化设计

缺点

  • 实现相对复杂,需要前后端配合
  • 依赖JavaScript,不支持纯静态环境

总结

在实际项目中,可以根据应用特点、用户需求和安全要求选择合适的验证码方案,甚至可以组合多种方案,在不同场景下使用不同的验证方式,既保障系统安全,又提供良好的用户体验。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/178793.html

(0)
上一篇 2025-05-17 12:45
下一篇 2025-05-17 13:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信