Spring Boot使用Kaptcha验证码

1. Maven依赖

pom.xml引入maven依赖。

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

2. Kaptcha配置类

@Configuration
class KaptchaConfig {
    @Bean
    fun producer(): DefaultKaptcha {
        val properties = Properties()
        properties.setProperty("kaptcha.border.color", "0,128,128")
        properties.setProperty("kaptcha.textproducer.font.color", "14,110,184")
        properties.setProperty("kaptcha.image.width", "92")
        properties.setProperty("kaptcha.image.height", "46")
        properties.setProperty("kaptcha.textproducer.font.size", "34")
        properties.setProperty("kaptcha.textproducer.char.length", "4")

        val config = Config(properties)
        val defaultKaptcha = DefaultKaptcha()
        defaultKaptcha.config = config
        return defaultKaptcha
    }
}

2.1 Kaptcha 详细配置表

Constant 描述 默认值
kaptcha.border 图片边框,合法值:yes , no yes
kaptcha.border.color 边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue. black
kaptcha.image.width 图片宽 200
kaptcha.image.height 图片高 50
kaptcha.producer.impl 图片实现类 com.google.code.kaptcha.impl.DefaultKaptcha
kaptcha.textproducer.impl 文本实现类 com.google.code.kaptcha.text.impl.DefaultTextCreator
kaptcha.textproducer.char.string 文本集合,验证码值从此集合中获取 abcde2345678gfynmnpwx
kaptcha.textproducer.char.length 验证码长度 5
kaptcha.textproducer.font.names 字体 Arial, Courier
kaptcha.textproducer.font.size 字体大小 40px.
kaptcha.textproducer.font.color 字体颜色,合法值: r,g,b 或者 white,black,blue. black
kaptcha.textproducer.char.space 文字间隔 2
kaptcha.noise.impl 干扰实现类 com.google.code.kaptcha.impl.DefaultNoise
kaptcha.noise.color 干扰 颜色,合法值: r,g,b 或者 white,black,blue. black
kaptcha.obscurificator.impl

图片样式:

水纹 com.google.code.kaptcha.impl.WaterRipple

鱼眼 com.google.code.kaptcha.impl.FishEyeGimpy

阴影 com.google.code.kaptcha.impl.ShadowGimpy

com.google.code.kaptcha.impl.WaterRipple
kaptcha.background.impl 背景实现类 com.google.code.kaptcha.impl.DefaultBackground
kaptcha.background.clear.from 背景颜色渐变,开始颜色 light grey
kaptcha.background.clear.to 背景颜色渐变, 结束颜色 white
kaptcha.word.impl 文字渲染器 com.google.code.kaptcha.text.impl.DefaultWordRenderer
kaptcha.session.key session key KAPTCHA_SESSION_KEY
kaptcha.session.date session date KAPTCHA_SESSION_DATE

3. Captcha实体类

captcha存储在数据库中。

@Entity
data class Captcha(
    @Column(nullable = false) val name: String,
    @Column(nullable = false) val code: String,
    @Column(nullable = false) val expireTime: Instant,
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Int? = null
)

4. Captcha仓库类

Spring Data JPA仓库类读取数据。

interface CaptchaRepository : JpaRepository<Captcha, Int> {
    fun findByName(name: String): Captcha?
    fun deleteAllByName(name: String)
}

5. Captcha控制器

生成验证码图片返回给前端。

@Controller
@RequestMapping("/captcha")
class CaptchaController(private val service: CaptchaService) {
    @GetMapping
    fun generate(name: String, response: HttpServletResponse) = service.generate(name, response)
}

6.Captcha服务类

生成验证码和验证。

@Service
class CaptchaService(private val repository: CaptchaRepository, private val kaptchaProducer: DefaultKaptcha) {
    @Transactional
    fun generate(name: String, response: HttpServletResponse) {
        val code = kaptchaProducer.createText()
        val image: BufferedImage = kaptchaProducer.createImage(code)
        repository.deleteAllByName(name)
        repository.save(Captcha(name, code, Instant.now().plusSeconds(60)))
        response.contentType = MediaType.IMAGE_JPEG_VALUE
        ImageIO.write(image, "jpg", response.outputStream)
    }

    fun validate(name: String, code: String?): Boolean {
        val captcha = repository.findByName(name) ?: return false

        if (Instant.now().isAfter(captcha.expireTime)) {
            repository.delete(captcha)
            return false
        }

        if (captcha.code == code) {
            repository.delete(captcha)
            return true
        }

        return false
    }
}

7. 验证验证码

  override fun preLogin(account: LoginDto) {
        val ip = getClientIp()
        val user = userRepository.findByUsername(account.username)
        val key = if (user == null) "$ip" else "${user.id}-$ip"
        val count = cache.getIfPresent(key) ?: 0

        if (count > 5 && !captchaService.validate(account.username, account.captcha)) {
            throw AppUnauthorizedException("验证码错误")
        }
    }