Implementing Captcha Login with Spring Boot and Redis
The guide demonstrates how to build a front‑end/back‑end separated captcha login using Spring Boot, Kaptcha and Redis, showing Redis‑based captcha storage with a unique key, Base64 image delivery, and verification logic that replaces traditional session‑based approaches.
This article introduces a complete captcha‑based login solution for a front‑end/back‑end separated architecture using Spring Boot, Kaptcha, and Redis.
Traditional (non‑separated) approach : The login request and captcha generation are handled within the same server process, relying on HTTP session to store the username, password, and captcha text.
Captcha generation flow (image) and login verification flow are illustrated with diagrams in the original article.
Front‑end/back‑end separated approach : To eliminate session dependency, the captcha text is stored in Redis with a unique identifier. The client receives the identifier together with the captcha image, and the server validates the captcha by fetching the stored value from Redis, returning a token upon successful authentication.
Kaptcha library : An open‑source captcha generator based on SimpleCaptcha.
com.github.penggle
kaptcha
2.3.2The project also depends on Spring Boot, Redis and related utilities.
Redis configuration class :
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate
redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate
redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}Kaptcha configuration class :
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha producer() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "no");
properties.setProperty("kaptcha.border.color", "105,179,90");
properties.setProperty("kaptcha.textproducer.font.color", "black");
properties.setProperty("kaptcha.image.width", "110");
properties.setProperty("kaptcha.image.height", "40");
properties.setProperty("kaptcha.textproducer.char.string", "23456789abcdefghkmnpqrstuvwxyzABCDEFGHKMNPRSTUVWXYZ");
properties.setProperty("kaptcha.textproducer.font.size", "30");
properties.setProperty("kaptcha.textproducer.char.space", "3");
properties.setProperty("kaptcha.session.key", "code");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}Captcha controller (exposes the image as a Base64 string):
package com.lzp.kaptcha.controller;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.lzp.kaptcha.service.CaptchaService;
import com.lzp.kaptcha.vo.CaptchaVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@RestController
@RequestMapping("/captcha")
public class CaptchaController {
@Autowired
private DefaultKaptcha producer;
@Autowired
private CaptchaService captchaService;
@ResponseBody
@GetMapping("/get")
public CaptchaVO getCaptcha() throws IOException {
String content = producer.createText();
BufferedImage image = producer.createImage(content);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", outputStream);
BASE64Encoder encoder = new BASE64Encoder();
String base64Img = "data:image/jpeg;base64," + encoder.encode(outputStream.toByteArray()).replace("\n", "").replace("\r", "");
CaptchaVO captchaVO = captchaService.cacheCaptcha(content);
captchaVO.setBase64Img(base64Img);
return captchaVO;
}
}CaptchaVO data object :
package com.lzp.kaptcha.vo;
public class CaptchaVO {
private String captchaKey;
private Long expire;
private String base64Img;
public String getCaptchaKey() { return captchaKey; }
public void setCaptchaKey(String captchaKey) { this.captchaKey = captchaKey; }
public Long getExpire() { return expire; }
public void setExpire(Long expire) { this.expire = expire; }
public String getBase64Img() { return base64Img; }
public void setBase64Img(String base64Img) { this.base64Img = base64Img; }
}Captcha service (stores captcha in Redis) :
package com.lzp.kaptcha.service;
import com.lzp.kaptcha.utils.RedisUtils;
import com.lzp.kaptcha.vo.CaptchaVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
public class CaptchaService {
@Value("${server.session.timeout:300}")
private Long timeout;
@Autowired
private RedisUtils redisUtils;
private final String CAPTCHA_KEY = "captcha:verification:";
public CaptchaVO cacheCaptcha(String captcha) {
String captchaKey = UUID.randomUUID().toString();
redisUtils.set(CAPTCHA_KEY.concat(captchaKey), captcha, timeout);
CaptchaVO captchaVO = new CaptchaVO();
captchaVO.setCaptchaKey(captchaKey);
captchaVO.setExpire(timeout);
return captchaVO;
}
}Login DTO (client request object) :
package com.lzp.kaptcha.dto;
public class LoginDTO {
private String userName;
private String pwd;
private String captchaKey;
private String captcha;
// getters and setters omitted for brevity
}User controller (login verification) :
package com.lzp.kaptcha.controller;
import com.lzp.kaptcha.dto.LoginDTO;
import com.lzp.kaptcha.utils.RedisUtils;
import com.lzp.kaptcha.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private RedisUtils redisUtils;
@PostMapping("/login")
public UserVO login(@RequestBody LoginDTO loginDTO) {
Object cached = redisUtils.get(loginDTO.getCaptchaKey());
// validate captcha, check user credentials, generate token, etc.
return new UserVO();
}
}The article also provides visual flow diagrams for both the traditional and the separated implementations, and lists additional resources such as interview tips and related articles.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.