Backend Development 12 min read

Implementing Captcha‑Based Login in a Frontend‑Backend Separated Spring Boot Application

This article demonstrates how to implement a captcha‑based login mechanism in a Spring Boot application with a front‑end/back‑end separation, detailing the traditional session‑based approach, the new Redis‑backed token solution, and providing complete code examples for configuration, service, controller, and data objects.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Captcha‑Based Login in a Frontend‑Backend Separated Spring Boot Application

The article begins with an outline that introduces the problem of brute‑force attacks on login systems and motivates the use of image captchas in a modern front‑end/back‑end separated architecture.

It first describes the classic, non‑separated login flow where the username, password, and captcha are stored in the HTTP session, showing the flow with simple diagrams.

Next, it explains the advantages of separating the front‑end and back‑end, and presents a new login scheme that stores the generated captcha in Redis, adds a unique identifier, and returns a token to the client, eliminating session dependence.

The tutorial then introduces the Kaptcha library, an open‑source captcha generator based on SimpleCaptcha, and shows how to add it to a Spring Boot project.

Project setup and Maven dependencies are provided:

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

The pom.xml for the Spring Boot project is also listed, including dependencies for Spring Web, Redis, Commons‑Pool2, FastJSON, and Jackson.

A Redis configuration class is defined to create a RedisTemplate<String, Object> with appropriate serializers:

@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;
    }
}

The KaptchaConfig class configures the visual properties of the captcha image:

@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.obscurificator.impl","com.xxx"); // optional custom implementation
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

The CaptchaController exposes an endpoint /captcha/get that generates a text captcha, creates an image, encodes it as a Base64 data URL, caches the text in Redis via CaptchaService , and returns a CaptchaVO containing the key, expiration, and image data:

@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;
    }
}

The data transfer objects are defined as follows:

public class CaptchaVO {
    private String captchaKey;
    private Long expire;
    private String base64Img;
    // getters and setters omitted for brevity
}
public class LoginDTO {
    private String userName;
    private String pwd;
    private String captchaKey;
    private String captcha;
    // getters and setters omitted for brevity
}

The CaptchaService generates a UUID as the captcha identifier, stores the captcha text in Redis with a configurable timeout, and returns a populated CaptchaVO :

@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 vo = new CaptchaVO();
        vo.setCaptchaKey(captchaKey);
        vo.setExpire(timeout);
        return vo;
    }
}

The user login endpoint ( /user/login ) retrieves the stored captcha from Redis, validates it against the user‑provided value, and (in a real implementation) would verify credentials and issue a JWT token. The example returns an empty UserVO placeholder:

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private RedisUtils redisUtils;
    @PostMapping("/login")
    public UserVO login(@RequestBody LoginDTO loginDTO) {
        Object cached = redisUtils.get(loginDTO.getCaptchaKey());
        // if (cached == null) throw new RuntimeException("Captcha expired");
        // if (!loginDTO.getCaptcha().equals(cached)) throw new RuntimeException("Captcha incorrect");
        // Validate user credentials, generate token, etc.
        return new UserVO();
    }
}

Finally, the article shows screenshots of the captcha generation and verification process, and includes a community invitation that is unrelated to the technical content.

BackendRedisSpring BootcaptchaloginKaptcha
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.