SpringBoot进阶教程(八十三)Kaptcha
Kaptcha是谷歌开源的一个可高度配置的比较老旧的实用验证码生成工具。它可以实现:(1)验证码的字体/大小颜色;(2)验证码内容的范围(数字,字母,中文汉字);(3)验证码图片的大小,边框,边框粗细,边框颜色(4)验证码的干扰线验证码的样式(鱼眼样式、3D、 普通模糊)。
v搭建架构
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha getDefaultKaptcha() {
com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.textproducer.font.color", "black");
properties.put("kaptcha.image.width", "200");
properties.put("kaptcha.image.height", "50");
properties.put("kaptcha.textproducer.font.size", "25");
properties.put("kaptcha.session.key", "verifyCode");
properties.put("kaptcha.textproducer.char.space", "5");
Config config = new Config(properties);
defaultKaptcha.setConfig(config); return defaultKaptcha;
}
}
此处配置的类可参考下方的配置表格:
| 常量 | 描述 | 默认值 |
|---|---|---|
| kaptcha.border | 图片边框,合法值:yes , no | yes |
| kaptcha.border.color | 边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue. | black |
| kaptcha.border.thickness | 边框厚度,合法值:>0 | 1 |
| kaptcha.image.width | 图片宽 | 200 |
| kaptcha.image.height | 图片高 | 50 |
| kaptcha.producer.impl | 图片实现类 | com.google.code.kaptcha.impl.DefaultKaptcha |
| kaptcha.textproducer.font.size | 文本实现类 | com.google.code.kaptcha.text.impl.DefaultTextCreator |
| 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.textproducer.char.length | 验证码长度 | 5 |
/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
@RestController
@RequestMapping("/demo")
@Slf4j
public class ImageController {
@Autowired
private DefaultKaptcha defaultKaptcha; @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
public void getKaptcha(HttpServletResponse response, HttpSession session) {
String text = defaultKaptcha.createText();
BufferedImage image = defaultKaptcha.createImage(text);
// 线上环境这个验证码肯定是要存redis里的,redis的key还需要设置一个合理的过期时间
session.setAttribute("kaptcha", text);
response.setContentType("image/png");
try {
ServletOutputStream os = response.getOutputStream();
ImageIO.write(image, "png", os);
} catch (IOException e) {
log.error("响应验证码失败:" + e.getMessage());
}
} @CrossOrigin
@RequestMapping(path = "/login", method = RequestMethod.POST)
public String login(HttpSession session, String kaptcha) {
if(kaptcha.equals(session.getAttribute("kaptcha"))){
return kaptcha + "验证码正确";
}else{
return kaptcha + "验证码错误";
}
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function refresh_kaptcha() {
var path = "http://localhost:8301/demo/kaptcha?p=" + Math.random();
document.getElementById("kaptcha").src=path;
} function login() {
var xhr = new XMLHttpRequest; xhr.open('post', 'http://localhost:8301/demo/login');
// post请求的参数要放在send方法中作为参数的 - 必须的字符串
// post请求要带参数必须在send之前设置 头信息
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
// 数据在传送之前需要进行编码
xhr.send('kaptcha=' + document.getElementById("kaptcha_value").value)
xhr.onreadystatechange = function () {
// 监听请求状态的变化 readystate (1-5 1准备发送 2 发送完成 3 发送完成数据准备接收 4数据
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
var res = xhr.responseText;
//res = JSON.parse(res)
alert(res);
}
}
}
}
</script>
</head>
<body>
<h3>请登录</h3>
<div class="col-sm-4">
<input type="text" placeholder="请输入用户名" name="username" required="required"/>
<br/>
<input type="password" placeholder="请输入密码" name="password" required="required"/>
<br/>
<span style="display: inline">
<input type="text" name="请输入验证码" id="kaptcha_value" placeholder="验证码" required="required"/>
<img src="http://localhost:8301/demo/kaptcha" id="kaptcha" style="width:100px;height:50px;" class="mr-2"/>
<a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">刷新验证码</a>
</span>
<br/>
<button type="submit" onclick="login()">登录</button>
</div>
</body>
</html>

v自定义验证码文本生成器
/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
public class CustomTextCreator extends DefaultTextCreator { private static final String[] Number = "0,1,2,3,4,5,6,7,8,9,10".split(",");
@Override
public String getText()
{
int result;
Random random = new Random();
int x = random.nextInt(10);
int y = random.nextInt(10);
StringBuilder suChinese = new StringBuilder();
int randomOperand = (int) Math.round(Math.random() * 2);
if (randomOperand == 0) {
result = x * y;
suChinese.append(Number[x]);
suChinese.append("*");
suChinese.append(Number[y]);
} else if (randomOperand == 1) {
if (!(x == 0) && y % x == 0) {
result = y / x;
suChinese.append(Number[y]);
suChinese.append("/");
suChinese.append(Number[x]);
} else {
result = x + y;
suChinese.append(Number[x]);
suChinese.append("+");
suChinese.append(Number[y]);
}
} else if (randomOperand == 2) {
if (x >= y) {
result = x - y;
suChinese.append(Number[x]);
suChinese.append("-");
suChinese.append(Number[y]);
} else {
result = y - x;
suChinese.append(Number[y]);
suChinese.append("-");
suChinese.append(Number[x]);
}
} else {
result = x + y;
suChinese.append(Number[x]);
suChinese.append("+");
suChinese.append(Number[y]);
}
suChinese.append("=?@").append(result);
return suChinese.toString();
}
}
/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
@Configuration
public class KaptchaConfig {
// @Bean
// public DefaultKaptcha getDefaultKaptcha() {
// com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
// Properties properties = new Properties();
// properties.put("kaptcha.border", "no");
// properties.put("kaptcha.textproducer.font.color", "black");
// properties.put("kaptcha.image.width", "200");
// properties.put("kaptcha.image.height", "50");
// properties.put("kaptcha.textproducer.font.size", "25");
// properties.put("kaptcha.session.key", "verifyCode");
// properties.put("kaptcha.textproducer.char.space", "5");
// Config config = new Config(properties);
// defaultKaptcha.setConfig(config);
//
// return defaultKaptcha;
// }
@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
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.key", "kaptchaCodeMath");
// 验证码文本生成器
properties.setProperty("kaptcha.textproducer.impl", "com.kaptcha.demo.util.CustomTextCreator");
// 验证码文本字符间距 默认为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");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
@RestController
@Slf4j
@RequestMapping("/custom")
public class CustomController { @Autowired
private Producer producer; public static final String DEFAULT_CODE_KEY = "random_code_"; /**
* @MethodName createCaptcha
* @Description 生成验证码
* @param httpServletResponse 响应流
* @Author hl
* @Date 2022/12/6 10:30
*/
@GetMapping("/create")
public void createCaptcha(HttpServletResponse httpServletResponse, HttpSession session) throws IOException {
// 生成验证码
String capText = producer.createText();
String capStr = capText.substring(0, capText.lastIndexOf("@"));
String result = capText.substring(capText.lastIndexOf("@") + 1);
BufferedImage image = producer.createImage(capStr);
// 保存验证码信息
String randomStr = UUID.randomUUID().toString().replaceAll("-", "");
System.out.println("随机数为:" + randomStr);
//redisTemplate.opsForValue().set(DEFAULT_CODE_KEY + randomStr, result, 3600, TimeUnit.SECONDS);
session.setAttribute("kaptcha", result);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try {
ImageIO.write(image, "jpg", os);
} catch (IOException e) {
log.error("ImageIO write err", e);
httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
} // 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
byte[] bytes = os.toByteArray();
//设置响应头
httpServletResponse.setHeader("Cache-Control", "no-store");
//设置响应头
httpServletResponse.setHeader("randomstr",randomStr);
//设置响应头
httpServletResponse.setHeader("Pragma", "no-cache");
//在代理服务器端防止缓冲
httpServletResponse.setDateHeader("Expires", 0);
//设置响应内容类型
ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
responseOutputStream.write(bytes);
responseOutputStream.flush();
responseOutputStream.close();
}
}


v前后端验证码实现流程扩展
- 前端向后端请求验证码。
- 后端通过谷歌开源工具Kaptcha生成图形验证码(实际是5个随机字符),缓存到redis,key键可以是 业务+用户id,value值就是那5个随机字符。设置TTL为2分钟。
- 后端将图形验证码转base64编码字符串,将该字符串返回给前端。
- 前端解析base64编码的字符串,即可在页面上显示图形验证码。
- 用户输入密码与验证码后提交表单到后端。
- 后端根据业务和用户id组成的key键到redis查找缓存的验证码信息,会有如下情况:
- 如果redis返回为空,则通知前端验证码失效,需要重新获取验证码。
- 如果redis返回不为空,但是不相等,说明验证码输入错误。则删除redis中对应验证码缓存,通知前端验证码错误,需要重新获取验证码。
- 如果redis返回不为空,并且相等,则校验成功,就删除redis中对应验证码缓存,并在mysql中修改密码。最后通知前端修改成功。
其他参考/学习资料:
- https://www.cnblogs.com/dreamOfHua/p/3532776.html
- https://blog.csdn.net/weixin_43296313/article/details/128207045
v源码地址
https://github.com/toutouge/javademosecond/tree/master/kaptcha-demo
作 者:请叫我头头哥
出 处:http://www.cnblogs.com/toutou/
关于作者:专注于基础平台的项目开发。如有问题或建议,请多多赐教!
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信我
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是作者坚持原创和持续写作的最大动力!
#comment_body_3242240 { display: none }
SpringBoot进阶教程(八十三)Kaptcha的更多相关文章
- SpringBoot进阶教程(二十三)Linux部署Quartz
在之前的一篇文章中<SpringBoot(九)定时任务Schedule>,已经详细介绍了关于schedule框架的配置和使用,有收到一些朋友关于部署的私信,所以抽时间整理一个linux部署 ...
- SpringBoot进阶教程(七十三)整合elasticsearch
Elasticsearch 是一个分布式.高扩展.高实时的搜索与数据分析引擎.它能很方便的使大量数据具有搜索.分析和探索的能力.充分利用Elasticsearch的水平伸缩性,能使数据在生产环境变得更 ...
- SpringBoot进阶教程(六十三)Jasypt配置文件加密
数据库密码直接明文写在配置中,对安全来说,是一个很大的挑战.一旦密码泄漏,将会带来很大的安全隐患.尤其在一些企业对安全性要求很高,因此我们就考虑如何对密码进行加密.本文着重介绍Jasypt对Sprin ...
- SpringBoot进阶教程(七十四)整合ELK
在上一篇文章<SpringBoot进阶教程(七十三)整合elasticsearch >,已经详细介绍了关于elasticsearch的安装与使用,现在主要来看看关于ELK的定义.安装及使用 ...
- SpringBoot进阶教程(六十八)Sentinel实现限流降级
前面两篇文章nginx限流配置和SpringBoot进阶教程(六十七)RateLimiter限流,我们介绍了如何使用nginx和RateLimiter限流,这篇文章介绍另外一种限流方式---Senti ...
- SpringBoot进阶教程(二十九)整合Redis 发布订阅
SUBSCRIBE, UNSUBSCRIBE 和 PUBLISH 实现了 发布/订阅消息范例,发送者 (publishers) 不用编程就可以向特定的接受者发送消息 (subscribers). Ra ...
- SpringBoot进阶教程(五十九)整合Codis
上一篇博文<详解Codis安装与部署>中,详细介绍了codis的安装与部署,这篇文章主要介绍介绍springboot整合codis.如果之前看过<SpringBoot进阶教程(五十二 ...
- SpringBoot进阶教程 | 第四篇:整合Mybatis实现多数据源
这篇文章主要介绍,通过Spring Boot整合Mybatis后如何实现在一个工程中实现多数据源.同时可实现读写分离. 准备工作 环境: windows jdk 8 maven 3.0 IDEA 创建 ...
- SpringBoot进阶教程(六十一)intellij idea project下建多个module搭建架构(下)
在上一篇文章<SpringBoot进阶教程(六十)intellij idea project下建多个module(上)>中,我们已经介绍了在intellij idea中创建project之 ...
- SpringBoot进阶教程(六十四)注解大全
在Spring1.x时代,还没出现注解,需要大量xml配置文件并在内部编写大量bean标签.Java5推出新特性annotation,为spring的更新奠定了基础.从Spring 2.X开始spri ...
随机推荐
- ubuntu18.04 heirloom-mailx 通过外部SMTP服务器发送邮件
配置软件源ubuntu18.04上无法直接安装heirloom-mailx,需要添加软件源 sudo vi /etc/apt/sources.list写入 deb http://cz.archive. ...
- Ubuntu 切换显示管理器
比较流行的显示管理器有: gdm3 - GNOME Display Manager lightdm - Light Display Manager sddm - Simple Desktop Disp ...
- Docker网络上篇-网络介绍
通过前面的学习,我们已经可以把自己写的微服务项目通过dockerfile文件方式部署到docker上面了.那么微服务之间通信,怎么通信的?是在同一个网络还是在不同的网络环境下?docker中怎么配置网 ...
- Java并发编程之验证volatile指令重排-理论篇
Java并发编程之验证volatile指令重排-理论篇 Java并发包下的类中大量使用了volatile关键字.通过之前文章介绍,大家已经知道了volatile的三大特性:共享变量可见性:不保证原子性 ...
- sicp每日一题[1.40]
Exercise 1.40 Define a procedure cubic that can be used together with the newtons-method procedure i ...
- 人形动画常见IK的处理
Unity中常见人形动画IK的处理方式 本文将尝试仅使用Untiy内置的Animator来解决常见的几种运动所需的IK.也会给出核心功能的代码实现. 效果一览:b站视频 Unity中人形角色的IK I ...
- 痛定思痛,好好做人,从头过一遍PyTorch框架(一)(1.深度学习简介、2.预备知识)
现在是2024年2月24日,13:59,从研一就开始断断续续说要过一遍框架,到现在博一下学期,还一直拖着呢,拖延症太可怕啦,决定好好做人,不拖了,就从现在开始,好好过一遍,呜呜呜呜呜呜呜呜,(罪该万死 ...
- 平面设计 – 色轮 & 配色
前言 由于之前那篇有点长, 而且色轮很重要, 所以独立写一篇呗. 参考: 一文看懂色轮 Youtube – 03 色彩 (什么是色相.纯度.明度.色环.补色?怎样配色?) Youtube – 初學繪畫 ...
- Codes 开源研发项目管理平台——创新的敏捷测试解决方案
前言 Codes 是国内首款重新定义 SaaS 模式的开源项目管理平台,支持云端认证.本地部署.全部功能开放,并且对 30 人以下团队免费.它通过整合迭代.看板.度量和自动化等功能,简化测试协同工作, ...
- Redis 发布订阅模式
概述 Redis 的发布/订阅是一种消息通信模式:发送者(Pub)向频道(Channel)发送消息,订阅者(Sub)接收频道上的消息.Redis 客户端可以订阅任意数量的频道,发送者也可以向任意频道发 ...