Backend Development 12 min read

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.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Implementing Captcha Login with Spring Boot and Redis

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.2

The 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.

JavaRediscaptchaAPIloginSpringBoot
Java Tech Enthusiast
Written by

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!

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.