更多精彩内容,请访问 Spring Boot组件集成实战专栏 !
推荐项目:一套基于Spring Boot+Layui的内容管理系统/快速开发脚手架(含完整的开发文档、演示网址等)
文章目录
- 1. 验证码的作用
- 2. Spring Boot集成Kaptcha
- 2.1 引入依赖
- 2.2 配置Kaptcha配置类
- 2.3 实现验证码服务层接口
- 3. 验证码的生成与使用
- 4. 本文源码下载
1. 验证码的作用
一个技术的出现,必然有它的道理。
关于验证码的定义,维基百科是这样解释的:
全自动区分计算机和人类的公开(英语: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
依赖,如下。
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.12</version>
</dependency>
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
这里为什么也引入hutool组件?
因为hutool组件也集成了验证码功能,所以这个项目中一并进行演示。
2.2 配置Kaptcha配置类
上图中我们已经给出了项目的架构,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<String, String> mathTextCreator(int a, int b) {
Random random = new SecureRandom();
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<String, String> ret = new HashMap<String, String>();
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();
Map<String, String> 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<String, String> result = MyCaptchaUtil.mathTextCreator(random.nextInt(100), random.nextInt(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();
properties.setProperty(KAPTCHA_BORDER, "yes");
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
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();
properties.setProperty(KAPTCHA_BORDER, "yes");
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, textImpl);
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
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()));
}
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(captchaDomain.getImage(), "jpg", outputStream);
BASE64Encoder encoder = new BASE64Encoder();
captchaDomain.setBase64("data:image/jpg;base64," + encoder.encode(outputStream.toByteArray()));
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();
Random random = new SecureRandom();
int type = random.nextInt(3);
AbstractCaptcha captcha = null;
if (type == 0) {
captcha = CaptchaUtil.createLineCaptcha(width, height);
}
else if (type == 1) {
captcha = CaptchaUtil.createCircleCaptcha(width, height);
}
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 "<html>\n" +
"\n" +
"<head>\n" +
"<title>验证码生成</title>\n" +
"</head>\n" +
"\n" +
"<body>\n" +
"<p>获取验证码实体:<a href=\"/captcha/get\" target=_blank>localhost:9003/captcha/get</a> (链接缺省type参数,默认生成文本验证码)<br></p>\n" +
"<p>获取验证码实体:<a href=\"/captcha/get?type=math\" target=_blank>localhost:9003/captcha/get?type=math</a><br></p>\n" +
"<p>获取验证码实体:<a href=\"/captcha/get?type=math2\" target=_blank>localhost:9003/captcha/get?type=math2</a><br></p>\n" +
"<p>获取验证码图片:<a href=\"/captcha/get/image?type=math\" target=_blank>localhost:9003/captcha/get/image?type=math</a> (此接口包含hutool验证码的测试)<br></p>\n" +
"<p>链接末尾可加的type参数有:char、math、math2" +
"</body>\n" +
"\n" +
"</html>\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) {
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;
Random random = new SecureRandom();
int rand = random.nextInt(2);
if (rand == 0) {
captchaDomain = captchaService.createGoogleCaptcha(type);
} else {
captchaDomain = captchaService.createHutoolCaptcha(160, 60);
}
System.out.println("Token:" + captchaDomain.getToken() + "\t验证码:" + captchaDomain.getCode());
HttpSession session = request.getSession();
session.setAttribute(captchaDomain.getToken(), captchaDomain.getCode());
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. 本文源码下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)