文章目录更多精彩内容,请访问 Spring Boot组件集成实战专栏 !
- 1. 验证码的作用
- 2. Spring Boot集成Kaptcha
- 2.1 引入依赖
- 2.2 配置Kaptcha配置类
- 2.3 实现验证码服务层接口
- 3. 验证码的生成与使用
- 4. 本文源码下载
一个技术的出现,必然有它的道理。
关于验证码的定义,维基百科是这样解释的:
全自动区分计算机和人类的公开(英语:Completely Automated Public Turing test to tell Computers and Humans Apart,简称CAPTCHA),又称验证码,是一种区分用户是机器或人类的公共全自动程序。
所以,通过其定义,即可得出其主要功能是帮助计算机判断当前的用户是机器还是人类,从而防止有人利用计算机程序对网站进行一些破坏性操作,比如留言板大量张贴广告等。
目前常用的验证码主要有图片验证码、声音验证码、滑块验证码等。
本文主要基于Kaptcha组件,实现两种常用的图片验证码:文本验证码和算式验证码。
2. Spring Boot集成Kaptcha注意:请详细阅读代码的注释!建议结合文末的本文源码阅读~
2.1 引入依赖1、新建一个Spring Boot项目,结构如下图所示。
2、在pom.xml中,引入kaptcha和hutool依赖,如下。
cn.hutool hutool-all 5.7.12 com.github.penggle kaptcha 2.3.2
2.2 配置Kaptcha配置类这里为什么也引入hutool组件?
因为hutool组件也集成了验证码功能,所以这个项目中一并进行演示。
上图中我们已经给出了项目的架构,com.cxhit.captcha 包下,包含config、controller、entity、service、utils 5个包。
3、在utils包下,新建一个名为MyCaptchaUtil的java class ,是一个工具类。
该类中的方法主要是生成数学类型的文本算式验证码,如1+1=?类型的验证码。
该工具类的完整源码如下。
package com.cxhit.captcha.utils;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class MyCaptchaUtil {
public static Map mathTextCreator(int a, int b) {
Random random = new SecureRandom();
// 生成随机操作,操作范围:[0,4),分别表示: + - * /
int op = random.nextInt(4);
// 定义计算的结果
Integer result = 0;
// 定义构建的算式字符串
StringBuilder resultString = new StringBuilder();
// 运算符:-
if (1 == op) {
if (a >= b) {
result = a - b;
resultString.append(a).append("-").append(b).append("=?@").append(result);
} else {
result = b - a;
resultString.append(b).append("-").append(a).append("=?@").append(result);
}
}
// 运算符:*
else if (2 == op) {
result = a * b;
resultString.append(a).append("*").append(b).append("=?@").append(result);
}
// 运算符:/
else if (3 == op) {
if (a != 0 && b % a == 0) {
result = b / a;
resultString.append(b).append("/").append(a).append("=?@").append(result);
} else if (b != 0 && a % b == 0) {
result = a / b;
resultString.append(a).append("/").append(b).append("=?@").append(result);
} else {
return mathTextCreator(a, b);
}
}
// 运算符:+
else {
result = b + a;
resultString.append(a).append("+").append(b).append("=?@").append(result);
}
Map ret = new HashMap();
ret.put("resultCode", result.toString());
ret.put("resultString", resultString.toString());
return ret;
}
}
4、在config包下,新建两个类:KaptchaMathOneTextCreator和KaptchaMathTwoTextCreator,并均继承自DefaultTextCreator。
这两个方法的主要作用是生成一位数和两位数的加减乘除算式验证码。
KaptchaMathOneTextCreator.java 源码如下。
package com.cxhit.captcha.config;
import com.cxhit.captcha.utils.MyCaptchaUtil;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Random;
public class KaptchaMathOneTextCreator extends DefaultTextCreator {
@Override
public String getText() {
Random random = new SecureRandom();
// 生成两个随机数,随机数范围:[0,10),并返回结果
Map result = MyCaptchaUtil.mathTextCreator(random.nextInt(10), random.nextInt(10));
return result.get("resultString");
}
}
KaptchaMathTwoTextCreator.java 源码如下。
package com.cxhit.captcha.config;
import com.cxhit.captcha.utils.MyCaptchaUtil;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Random;
public class KaptchaMathTwoTextCreator extends DefaultTextCreator {
@Override
public String getText() {
Random random = new SecureRandom();
// 保存计算结果
Map result = MyCaptchaUtil.mathTextCreator(random.nextInt(100), random.nextInt(100));
// 生成两个随机数,随机数范围:[0,100),并返回结果
return result.get("resultString");
}
}
5、在config包下,新建名为KaptchaConfig的java class,写入如下配置信息,配置的详情见代码注释。
package com.cxhit.captcha.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
import static com.google.code.kaptcha.Constants.*;
@Configuration
public class KaptchaConfig {
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框。默认true,可选:yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 验证码文本字符颜色。默认Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
// 验证码图片宽度。默认200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度。默认50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小。默认40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
// 验证码文本字符长度。默认5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 验证码文本字体样式。默认:new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 图片样式。
// 水纹:com.google.code.kaptcha.impl.WaterRipple
// 鱼眼:com.google.code.kaptcha.impl.FishEyeGimpy
// 阴影:com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
@Bean(name = "captchaProducerMathOne")
public DefaultKaptcha getKaptchaBeanMathOne() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = commonConfig("com.cxhit.captcha.config.KaptchaMathOneTextCreator");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
@Bean(name = "captchaProducerMathTwo")
public DefaultKaptcha getKaptchaBeanMathTwo() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = commonConfig("com.cxhit.captcha.config.KaptchaMathTwoTextCreator");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
protected static Properties commonConfig(String textImpl) {
Properties properties = new Properties();
// 是否有边框。默认为true,可设置:yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 边框颜色。默认:Color.BLACK
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
// 验证码文本字符颜色。默认:Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
// 验证码图片宽度。默认:200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度。默认:50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小。默认:40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
// 验证码文本生成器
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, textImpl);
// 验证码文本字符间距。默认:2
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
// 验证码文本字符长度。默认:5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
// 验证码文本字体样式。默认:new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 验证码噪点颜色。默认:Color.BLACK
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
// 干扰实现类
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
// 图片样式。
// 水纹:com.google.code.kaptcha.impl.WaterRipple
// 鱼眼:com.google.code.kaptcha.impl.FishEyeGimpy
// 阴影:com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
// 返回生成的配置类
return properties;
}
}
该配置文件主要是配置了三种类型的验证码生成器:
- 简单文本验证码
- 一位数加减乘除算式验证码
- 两位数加减乘除算式验证码
至此,完成的配置文件如下图所示。
2.3 实现验证码服务层接口6、在entity包下,新建名为CaptchaDomain的实体,用来进行验证码的数据传输。
其详细源码如下所示。
package com.cxhit.captcha.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.awt.image.BufferedImage;
import java.io.Serializable;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CaptchaDomain implements Serializable {
private static final long serialVersionUID = 1L;
private String token;
@JsonIgnore
private String text;
@JsonIgnore
private String code;
@JsonIgnore
private BufferedImage image;
private String base64;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
}
public String getbase64() {
return base64;
}
public void setbase64(String base64) {
this.base64 = base64;
}
@Override
public String toString() {
return "CaptchaDomain{" +
"token='" + token + ''' +
", text='" + text + ''' +
", code='" + code + ''' +
", image=" + image +
", base64='" + base64 + ''' +
'}';
}
}
7、在service包下,新建名为ICaptchaService的服务层接口。详细源码如下。
package com.cxhit.captcha.service;
import com.cxhit.captcha.entity.CaptchaDomain;
public interface ICaptchaService {
public CaptchaDomain createGoogleCaptcha(String type);
public CaptchaDomain createHutoolCaptcha(Integer width, Integer height);
}
只有在服务层接口中,我们才集成了hutool的验证码功能。所以说,hutool的验证码生成似乎更简单。
8、在service.impl包下,新建名为ICaptchaServiceImpl的类,并实现ICaptchaService的接口。详细源码如下。
package com.cxhit.captcha.service.impl;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.CaptchaUtil;
import com.cxhit.captcha.entity.CaptchaDomain;
import com.cxhit.captcha.service.ICaptchaService;
import com.google.code.kaptcha.Producer;
import org.springframework.stereotype.Service;
import sun.misc.base64Encoder;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.io.ByteArrayOutputStream;
import java.security.SecureRandom;
import java.util.Random;
import java.util.UUID;
@Service
public class ICaptchaServiceImpl implements ICaptchaService {
@Resource(name = "captchaProducer")
private Producer captchaProducer;
@Resource(name = "captchaProducerMathOne")
private Producer captchaProducerMathOne;
@Resource(name = "captchaProducerMathTwo")
private Producer captchaProducerMathTwo;
private static final String TYPE_CHAR = "char";
private static final String TYPE_MATH_ONE = "math";
private static final String TYPE_MATH_TWO = "math2";
@Override
public CaptchaDomain createGoogleCaptcha(String type) {
// 定义验证码实体
CaptchaDomain captchaDomain = new CaptchaDomain();
// 一位数加减乘除
if (TYPE_MATH_ONE.equals(type)) {
// 生成文本
String producerText = captchaProducerMathOne.createText();
// 设置验证码字符
captchaDomain.setText(producerText.substring(0, producerText.indexOf("@")));
// 设置验证码答案码
captchaDomain.setCode(producerText.substring(producerText.indexOf("@") + 1));
// 设置验证码图片
captchaDomain.setImage(captchaProducerMathOne.createImage(captchaDomain.getText()));
}
// 两位数加减乘除
else if (TYPE_MATH_TWO.equals(type)) {
String producerText = captchaProducerMathTwo.createText();
captchaDomain.setText(producerText.substring(0, producerText.indexOf("@")));
captchaDomain.setCode(producerText.substring(producerText.indexOf("@") + 1));
captchaDomain.setImage(captchaProducerMathTwo.createImage(captchaDomain.getText()));
}
// 缺省情况:字符
else {
captchaDomain.setText(captchaProducer.createText());
captchaDomain.setCode(captchaDomain.getText());
captchaDomain.setImage(captchaProducer.createImage(captchaDomain.getText()));
}
// 生成base64
try {
// 定义字节数组输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 将图像以 jpg 的形式,写到字节数组输出流中
ImageIO.write(captchaDomain.getImage(), "jpg", outputStream);
// 对字节数组base64编码
base64Encoder encoder = new base64Encoder();
// 写入base64格式
captchaDomain.setbase64("data:image/jpg;base64," + encoder.encode(outputStream.toByteArray()));
// 写入唯一Token
captchaDomain.setToken(UUID.randomUUID().toString());
// 返回结果
return captchaDomain;
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
@Override
public CaptchaDomain createHutoolCaptcha(Integer width, Integer height) {
CaptchaDomain captchaDomain = new CaptchaDomain();
// 生成 0、1、2 三个整数其一,随机对应下面的三种验证码类型
// 注意:这里只是尽可能多的展示每种验证码的使用方法,实际项目中选择一种即可。
Random random = new SecureRandom();
int type = random.nextInt(3);
AbstractCaptcha captcha = null;
// 【0】生成 线段干扰验证码
if (type == 0) {
//定义图形验证码的长和宽
captcha = CaptchaUtil.createLineCaptcha(width, height);
}
// 【1】生成 圆圈干扰验证码
else if (type == 1) {
//定义图形验证码的长、宽。还可以设置两个参数:验证码字符数、干扰元素个数
captcha = CaptchaUtil.createCircleCaptcha(width, height);
}
// 【2】生成 扭曲干扰验证码
else {
captcha = CaptchaUtil.createShearCaptcha(width, height);
}
// 信息配置
captchaDomain.setText(captcha.getCode());
captchaDomain.setCode(captcha.getCode());
captchaDomain.setbase64(captcha.getImagebase64Data());
captchaDomain.setImage(captcha.getImage());
captchaDomain.setToken(UUID.randomUUID().toString());
return captchaDomain;
}
}
至此,我们已经在项目中完成了验证码的集成工作。
截止当前,完成的文件如下图所示。
3. 验证码的生成与使用我们先分析一下验证码在不同框架中的使用流程。
- 前后端不分离:
这种框架下,可以使用session、Redis、MySQL等存储验证码。
每个会话的属性等配置信息,均以Session存储在服务端内存中,可以使用Session ID(会话的Key)对访问用户的身份进行判断和区分。
- 前后端分离:
这种框架下,一般使用Redis、MySQL等存储验证码,使用Token+验证码的形式,来对访问用户身份进行判断和区分。
其实现方案就是后端生成验证码的同时,为该验证码生成一个唯一的Token。将验证码图片和Token返回给前端,验证码答案和Token存储在数据库中,并设置过期时间。前端用户提交验证码的同时,也将Token一起提交给后端,Token负责架起用户输入的验证码和正确验证码之间的桥梁。
所以,在接下来的控制类接口中,我们将模拟这两种场景。
代码的逻辑,请见代码注释!
CaptchaController的完整源码如下。
package com.cxhit.captcha.controller;
import com.cxhit.captcha.entity.CaptchaDomain;
import com.cxhit.captcha.service.ICaptchaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;
@Controller
@RequestMapping("/")
public class CaptchaController {
@Autowired
private ICaptchaService captchaService;
@Resource
protected HttpServletRequest request;
@Resource
protected HttpServletResponse response;
@GetMapping("")
@ResponseBody
public String index() {
return "n" +
"n" +
"n" +
"验证码生成 n" +
"n" +
"n" +
"n" +
"获取验证码实体:localhost:9003/captcha/get (链接缺省type参数,默认生成文本验证码)
n" +
"获取验证码实体:localhost:9003/captcha/get?type=math
n" +
"获取验证码实体:localhost:9003/captcha/get?type=math2
n" +
"获取验证码图片:localhost:9003/captcha/get/image?type=math (此接口包含hutool验证码的测试)
n" +
"链接末尾可加的type参数有:char、math、math2" +
"n" +
"n" +
"n";
}
@GetMapping("/captcha/get")
@ResponseBody
public CaptchaDomain getCaptcha(@RequestParam(value = "type", required = false, defaultValue = "char") String type) {
// 生成验证码实体
CaptchaDomain captchaDomain = captchaService.createGoogleCaptcha(type);
if (null != captchaDomain) {
// 将验证码保存至redis
// redisUtils.set(captchaDomain.getToken(), captchaDomain.getCode(), 300L);
// 判断验证码正确
// if (null != redisUtils.get(captchaToken) && redisUtils.get(captchaToken).toString().equals(captcha)) {
// System.out.println("验证码正确!继续执行验证码正确后的操作");
// }
// 打印测试
System.out.println("Token:" + captchaDomain.getToken() + "t验证码:" + captchaDomain.getCode());
// 无用信息设空
captchaDomain.setText(null);
captchaDomain.setCode(null);
// 返回前端信息
return captchaDomain;
} else {
return null;
}
}
@GetMapping("/captcha/get/image")
public void getCaptchaImage(@RequestParam(value = "type", required = false, defaultValue = "char") String type) {
CaptchaDomain captchaDomain = null;
// 此处生成0或1的随机数,以随机测试谷歌验证码和hutool验证码
Random random = new SecureRandom();
int rand = random.nextInt(2);
if (rand == 0) {
// 生成谷歌验证码实体
captchaDomain = captchaService.createGoogleCaptcha(type);
} else {
// 生成hutool验证码实体
captchaDomain = captchaService.createHutoolCaptcha(160, 60);
}
// 打印验证码
System.out.println("Token:" + captchaDomain.getToken() + "t验证码:" + captchaDomain.getCode());
// 保存至session
HttpSession session = request.getSession();
session.setAttribute(captchaDomain.getToken(), captchaDomain.getCode());
// 从session中读取测试测试
System.out.println("根据Token:" + captchaDomain.getToken() + ",从session中读取验证码:" + session.getAttribute(captchaDomain.getToken()));
// 以文件流的形式,输出验证码图片
ServletOutputStream out = null;
try {
response.setContentType("image/jpeg");
out = response.getOutputStream();
ImageIO.write(captchaDomain.getImage(), "jpg", out);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
启动项目,访问 localhost:9003 (这里的端口在application.yml里配置),如下图所示。
访问前三个链接,返回的数据格式如下图所示。
将base64的值,复制到浏览器的新建标签页中,即可查看生成的验证码图片。如下图所示。
主要前后不要加引号。
后台可以看到打印的测试内容。如下图所示。
直接访问第四个链接,可以直接生成图片验证码,如下图所示。
后台同样可以看到打印的测试内容,如下图所示。
至此,关于验证码的全部功能,测试完毕。
4. 本文源码下载


