How to Build a Scalable Front‑Back‑Separated Captcha Login with Spring Boot and Redis
This article walks through the problem of traditional session‑based captcha login, compares it with a modern front‑back‑separated architecture, and provides a step‑by‑step implementation using Spring Boot, Kaptcha, and Redis, including full code snippets, configuration classes, and flow diagrams.
Problem Overview
Traditional monolithic web applications store the generated captcha text in the HTTP session. The login request only needs to submit username, password and captcha, but the flow is tightly coupled to the session and the back‑end renders the page.
Monolithic Captcha Flow
Separated Architecture
In a front‑back‑separated microservice architecture the HTTP session is unavailable. The solution moves captcha storage to Redis and adds a UUID token to bind the captcha to a specific request, making the login stateless.
Separated Captcha Generation
Separated Login Verification
Implementation
1. Maven Dependencies
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>2. Redis Configuration
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(factory);
return template;
}
}3. Kaptcha Configuration
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha producer() {
DefaultKaptcha kaptcha = new DefaultKaptcha();
Properties p = new Properties();
p.setProperty("kaptcha.border", "no");
p.setProperty("kaptcha.border.color", "105,179,90");
p.setProperty("kaptcha.textproducer.font.color", "black");
p.setProperty("kaptcha.image.width", "110");
p.setProperty("kaptcha.image.height", "40");
p.setProperty("kaptcha.textproducer.char.string",
"23456789abcdefghkmnpqrstuvwxyzABCDEFGHKMNPRSTUVWXYZ");
p.setProperty("kaptcha.textproducer.font.size", "30");
p.setProperty("kaptcha.textproducer.char.space", "3");
p.setProperty("kaptcha.session.key", "code");
p.setProperty("kaptcha.textproducer.char.length", "4");
p.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
p.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
kaptcha.setConfig(new Config(p));
return kaptcha;
}
}4. Captcha Generation Endpoint
@RestController
@RequestMapping("/captcha")
public class CaptchaController {
@Autowired private DefaultKaptcha producer;
@Autowired private CaptchaService captchaService;
@GetMapping("/get")
public CaptchaVO getCaptcha() throws IOException {
String text = producer.createText();
BufferedImage image = producer.createImage(text);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", out);
String base64 = "data:image/jpeg;base64," +
new BASE64Encoder().encode(out.toByteArray())
.replace("
", "").replace("\r", "");
CaptchaVO vo = captchaService.cacheCaptcha(text);
vo.setBase64Img(base64);
return vo;
}
}5. Captcha Value Object
public class CaptchaVO {
private String captchaKey; // UUID
private Long expire; // seconds
private String base64Img;
// getters and setters omitted for brevity
}6. Captcha Service (Redis Cache)
@Service
public class CaptchaService {
@Value("${server.session.timeout:300}")
private Long timeout;
@Autowired private RedisUtils redisUtils;
private static final String PREFIX = "captcha:verification:";
public CaptchaVO cacheCaptcha(String text) {
String uuid = UUID.randomUUID().toString();
redisUtils.set(PREFIX + uuid, text, timeout);
CaptchaVO vo = new CaptchaVO();
vo.setCaptchaKey(uuid);
vo.setExpire(timeout);
return vo;
}
}7. Login DTO
public class LoginDTO {
private String userName;
private String pwd;
private String captchaKey;
private String captcha;
// getters and setters omitted
}8. Login Endpoint
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired private RedisUtils redisUtils;
@PostMapping("/login")
public UserVO login(@RequestBody LoginDTO dto) {
Object stored = redisUtils.get(dto.getCaptchaKey());
if (stored == null) {
throw new RuntimeException("Captcha expired");
}
if (!dto.getCaptcha().equals(stored)) {
throw new RuntimeException("Captcha mismatch");
}
// Validate user credentials (omitted)
// Generate token and build UserVO (omitted)
return new UserVO();
}
}Result
Moving captcha storage from the HTTP session to Redis and introducing a UUID token removes session dependence, yielding a stateless login flow that fits front‑back‑separated microservice architectures.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
